diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 3d1f2b5781..c8d7ee4458 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -36,7 +36,7 @@ This Code of Conduct applies within all community spaces, and also applies when ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Gitter](https://gitter.im/gofiber/community). All complaints will be reviewed and investigated promptly and fairly. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Discord](https://gofiber.io/discord). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index da8566f7d9..7a84e21e40 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -When contributing to this repository, please first discuss the change you wish to make via our [Telegram](https://t.me/gofiber) group, by creating an [issue](https://github.com/gofiber/fiber/issues) or any other method with the owners of this repository before making a change. +When contributing to this repository, please first discuss the change you wish to make via our [Discord](https://gofiber.io/discord) server, by creating an [issue](https://github.com/gofiber/fiber/issues) or any other method with the owners of this repository before making a change. Please note: we have a [code of conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md), please follow it in all your interactions with the `Fiber` project. @@ -24,4 +24,4 @@ If you want to say **thank you** and/or support the active development of `Fiber 1. Add a [GitHub Star](https://github.com/gofiber/fiber/stargazers) to the project. 2. Tweet about the project [on your Twitter](https://twitter.com/intent/tweet?text=%F0%9F%9A%80%20Fiber%20%E2%80%94%20is%20an%20Express.js%20inspired%20web%20framework%20build%20on%20Fasthttp%20for%20%23Go%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber). 3. Write a review or tutorial on [Medium](https://medium.com/), [Dev.to](https://dev.to/) or personal blog. -4. Support the project by donating a [cup of coffee](https://buymeacoff.ee/fenny). \ No newline at end of file +4. Support the project by donating a [cup of coffee](https://buymeacoff.ee/fenny). diff --git a/.github/ISSUE_TEMPLATE/---bug.md b/.github/ISSUE_TEMPLATE/---bug.md deleted file mode 100644 index a250292311..0000000000 --- a/.github/ISSUE_TEMPLATE/---bug.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: "\U0001F41B Bug" -about: Create a report to help us improve -title: "\U0001F41B " -labels: 'Type: Bug' -assignees: '' - ---- - -**Fiber version** - -**Issue description** - -**Code snippet** - -```go -package main - -import "github.com/gofiber/fiber/v2" - -func main() { - app := fiber.New() - - // Steps to reproduce - - log.Fatal(app.Listen(":3000")) -} -``` diff --git a/.github/ISSUE_TEMPLATE/---feature.md b/.github/ISSUE_TEMPLATE/---feature.md deleted file mode 100644 index c3baf503a2..0000000000 --- a/.github/ISSUE_TEMPLATE/---feature.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "\U0001F680 Feature" -about: Suggest an idea for this project -title: "\U0001F680 " -labels: 'Type: Feature' -assignees: '' - ---- - -**Is your feature request related to a problem?** - -**Describe the solution you'd like** - -**Describe alternatives you've considered** - -**Additional context** diff --git a/.github/ISSUE_TEMPLATE/---question.md b/.github/ISSUE_TEMPLATE/---question.md deleted file mode 100644 index 68912c2b38..0000000000 --- a/.github/ISSUE_TEMPLATE/---question.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: "\U0001F917 Question" -about: Ask a question so we can help -title: "\U0001F917 " -labels: 'Type: Question' -assignees: '' - ---- - -**Question description** - -**Code snippet** _Optional_ - -```go -package main - -import "github.com/gofiber/fiber/v2" - -func main() { - app := fiber.New() - // .. -} -``` diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 0000000000..0452c7c5a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,85 @@ +name: "\U0001F41B Bug Report" +title: "\U0001F41B [Bug]: " +description: Create a bug report to help us fix it. +labels: ["☢️ Bug"] + +body: + - type: markdown + id: notice + attributes: + value: | + ### Notice + **This repository is not related to external or third-part Fiber modules. If you have a problem with them, open an issue under their repos. If you think the problem is related to Fiber, open the issue here.** + - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). + - If you think Fiber doesn't have a nice feature that you think, open the issue with **✏️ Feature Request** template. + - Write your issue with clear and understandable English. + - type: textarea + id: description + attributes: + label: "Bug Description" + description: "A clear and detailed description of what the bug is." + placeholder: "Explain your problem as clear and detailed." + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: How to Reproduce + description: "Steps to reproduce the behavior and what should be observed in the end." + placeholder: "Tell us step by step how we can replicate your problem and what we should see in the end." + value: | + Steps to reproduce the behavior: + 1. Go to '....' + 2. Click on '....' + 3. Do '....' + 4. See '....' + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: "A clear and detailed description of what you think should happens." + placeholder: "Tell us what Fiber should normally do." + validations: + required: true + - type: input + id: version + attributes: + label: "Fiber Version" + description: "Some bugs may be fixed in future Fiber releases, so we have to know your Fiber version." + placeholder: "Write your Fiber version. (v2.33.0, v2.34.1...)" + validations: + required: true + - type: textarea + id: snippet + attributes: + label: "Code Snippet (optional)" + description: "For some issues, we need to know some parts of your code." + placeholder: "Share a code you think related to the issue." + render: go + value: | + package main + + import "github.com/gofiber/fiber/v3" + import "log" + + func main() { + app := fiber.New() + + // Steps to reproduce + + log.Fatal(app.Listen(":3000")) + } + - type: checkboxes + id: terms + attributes: + label: "Checklist:" + description: "By submitting this issue, you confirm that:" + options: + - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." + required: true + - label: "I have checked for existing issues that describe my problem prior to opening this one." + required: true + - label: "I understand that improperly formatted bug reports may be closed without explanation." + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 0000000000..83bbcafdc8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,60 @@ +name: "\U0001F680 Feature Request" +title: "\U0001F680 [Feature]: " +description: Suggest an idea to improve this project. +labels: ["✏️ Feature"] + +body: + - type: markdown + id: notice + attributes: + value: | + ### Notice + - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). + - If you think this is just a bug, open the issue with **☢️ Bug Report** template. + - Write your issue with clear and understandable English. + - type: textarea + id: description + attributes: + label: "Feature Description" + description: "A clear and detailed description of the feature we need to do." + placeholder: "Explain your feature as clear and detailed." + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: "Additional Context (optional)" + description: "If you have something else to describe, write them here." + placeholder: "Write here what you can describe differently." + - type: textarea + id: snippet + attributes: + label: "Code Snippet (optional)" + description: "Code snippet may be really helpful to describe some features." + placeholder: "Share a code to explain the feature better." + render: go + value: | + package main + + import "github.com/gofiber/fiber/v3" + import "log" + + func main() { + app := fiber.New() + + // An example to describe the feature + + log.Fatal(app.Listen(":3000")) + } + - type: checkboxes + id: terms + attributes: + label: "Checklist:" + description: "By submitting this issue, you confirm that:" + options: + - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." + required: true + - label: "I have checked for existing issues that describe my suggestion prior to opening this one." + required: true + - label: "I understand that improperly formatted feature requests may be closed without explanation." + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yaml b/.github/ISSUE_TEMPLATE/question.yaml new file mode 100644 index 0000000000..6e06c16788 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yaml @@ -0,0 +1,55 @@ +name: "🤔 Question" +title: "\U0001F917 [Question]: " +description: Ask a question so we can help you easily. +labels: ["🤔 Question"] + +body: + - type: markdown + id: notice + attributes: + value: | + ### Notice + - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). + - If you think this is just a bug, open the issue with **☢️ Bug Report** template. + - If you think Fiber doesn't have a nice feature that you think, open the issue with **✏️ Feature Request** template. + - Write your issue with clear and understandable English. + - type: textarea + id: description + attributes: + label: "Question Description" + description: "A clear and detailed description of the question." + placeholder: "Explain your question as clear and detailed." + validations: + required: true + - type: textarea + id: snippet + attributes: + label: "Code Snippet (optional)" + description: "Code snippet may be really helpful to describe some features." + placeholder: "Share a code to explain the feature better." + render: go + value: | + package main + + import "github.com/gofiber/fiber/v3" + import "log" + + func main() { + app := fiber.New() + + // An example to describe the question + + log.Fatal(app.Listen(":3000")) + } + - type: checkboxes + id: terms + attributes: + label: "Checklist:" + description: "By submitting this issue, you confirm that:" + options: + - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." + required: true + - label: "I have checked for existing issues that describe my questions prior to opening this one." + required: true + - label: "I understand that improperly formatted questions may be closed without explanation." + required: true diff --git a/.github/README.md b/.github/README.md index 15376451c9..083fec6468 100644 --- a/.github/README.md +++ b/.github/README.md @@ -5,81 +5,82 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - + +

Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development with zero memory allocation and performance in mind. @@ -90,7 +91,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -105,7 +106,7 @@ func main() { ## 🤖 Benchmarks -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 results, please visit our [Wiki](https://docs.gofiber.io/extra/benchmarks). +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).

@@ -119,7 +120,7 @@ Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Features @@ -135,7 +136,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Translated in [15 languages](https://docs.gofiber.io/) +- Translated in [18 languages](https://docs.gofiber.io/) - And much more, [explore Fiber](https://docs.gofiber.io/) ## 💡 Philosophy @@ -147,7 +148,7 @@ Fiber is **inspired** by Express, the most popular web framework on the Internet We **listen** to our users in [issues](https://github.com/gofiber/fiber/issues), Discord [channel](https://gofiber.io/discord) _and all over the Internet_ to create a **fast**, **flexible** and **friendly** Go web framework for **any** task, **deadline** and developer **skill**! Just like Express does in the JavaScript world. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Examples @@ -295,7 +296,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -362,8 +363,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -385,8 +386,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -470,8 +471,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -504,7 +505,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -545,8 +546,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -570,8 +571,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -594,13 +595,14 @@ func main() { Here is a list of middleware that are included within the Fiber framework. | Middleware | Description | -| :------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|:---------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Basic auth middleware provides an HTTP basic authentication. It calls the next handler for valid credentials and 401 Unauthorized for missing or invalid credentials. | | [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Intercept and cache responses | | [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Compression middleware for Fiber, it supports `deflate`, `gzip` and `brotli` by default. | | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_ckb.md b/.github/README_ckb.md index d34d642699..9b3b2558ae 100644 --- a/.github/README_ckb.md +++ b/.github/README_ckb.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ func main() { پڕۆژەکەت دەست پێ بکە بە دروستکردنی بوخچەیەک و کار پێ کردنی فەرمانی `go mod init github.com/your/repo` ([زیاتر](https://go.dev/blog/using-go-modules)) لەناو بوخچەکە. دواتریش بەم فەرمانەی خوارەوە فایبەر دامەزرێنە: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 تایبەتمەندییەکان @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - پشتگیریی [WebSocket](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- وەرگێڕراوە بۆ [15 زمان](https://docs.gofiber.io/) +- وەرگێڕراوە بۆ [18 زمان](https://docs.gofiber.io/) - زیاتریش، [فایبەر بپشکنە](https://docs.gofiber.io/) ## 💡 فەلسەفە @@ -295,7 +295,7 @@ func main() { package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -362,8 +362,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -385,8 +385,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -470,8 +470,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -504,7 +504,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -545,8 +545,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -570,8 +570,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -601,6 +601,7 @@ func main() { | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_de.md b/.github/README_de.md index 949006341c..c34d90fcc8 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Stelle sicher, dass du Go installiert hast ([Download hier](https://go.dev/dl/)) Erstelle ein neues Project, indem du zunächst einen neuen Ordner erstellst und dort in diesem Ordner `go mod init github.com/dein/repo` ausführst ([hier mehr dazu](https://go.dev/blog/using-go-modules)). Daraufhin kannst du Fiber mit dem [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) Kommandozeilenbefehl installieren: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Eigenschaften @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Verfügbar in [15 Sprachen](https://docs.gofiber.io/) +- Verfügbar in [18 Sprachen](https://docs.gofiber.io/) - Und vieles mehr - [erkunde Fiber](https://docs.gofiber.io/) ## 💡 Philosophie @@ -142,7 +142,7 @@ Neue Gopher, welche von [Node.js](https://nodejs.org/en/about/) zu [Go](https:// Fiber ist **inspiriert** von Express.js, dem beliebtesten Web-Framework im Internet. Wir haben die **Leichtigkeit** von Express und die **Rohleistung** von Go kombiniert. Wenn du jemals eine Webanwendung mit Node.js implementiert hast (_mit Express.js oder ähnlichem_), werden dir viele Methoden und Prinzipien **sehr vertraut** vorkommen. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Beispiele @@ -290,7 +290,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -357,8 +357,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -380,8 +380,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -465,8 +465,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -499,7 +499,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -540,8 +540,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -571,6 +571,7 @@ Hier finden Sie eine Liste der Middleware, die im Fiber-Framework enthalten ist. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_es.md b/.github/README_es.md index 6a74760813..21c9c4a96f 100644 --- a/.github/README_es.md +++ b/.github/README_es.md @@ -5,81 +5,81 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

-Fiber es un framework web inspirado en Express construido sobre Fasthttp, el motor HTTP más rápido para Go. Diseñado para facilitar las cosas para un desarrollo rápido con cero asignación de memoria y rendimiento en mente. +Fiber es un framework web inspirado en Express construido sobre Fasthttp, el motor HTTP más rápido para Go. Diseñado para facilitar las cosas y tener un menor tiempo de desarrollo con cero asignación de memoria y pensado para un mejor rendimiento.

## ⚡️ Inicio rápido @@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Asegúrese de tener instalado Go ([descargar](https://go.dev/dl/)). Versión `1. Arranque su proyecto creando una nueva carpeta y ejecutando `go mod init github.com/your/repo` ([mas información](https://go.dev/blog/using-go-modules)) dentro del mismo directorio. Después instale Fiber mediante el comando [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Características @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Disponible en [15 idiomas](https://docs.gofiber.io/) +- Disponible en [18 idiomas](https://docs.gofiber.io/) - Y mucho más, [explora Fiber](https://docs.gofiber.io/) ## 💡 Filosofía @@ -142,7 +142,7 @@ Los nuevos gophers que hacen el cambio de [Node.js](https://nodejs.org/en/about/ Fiber está **inspirado** en Expressjs, el framework web más popular en Internet. Combinamos la **facilidad** de Express y **el rendimiento bruto** de Go. Si alguna vez ha implementado una aplicación web en Node.js ( _utilizando Express.js o similar_ ), muchos métodos y principios le parecerán **muy comunes** . ## ⚠️ Limitantes -* Debido a que Fiber utiliza unsafe, la biblioteca no siempre será compatible con la última versión de Go. Fiber 2.29.0 ha sido probado con las versiones de Go 1.14 a 1.18. +* Debido a que Fiber utiliza unsafe, la biblioteca no siempre será compatible con la última versión de Go. Fiber 2.40.0 ha sido probado con las versiones de Go 1.16 a 1.19. * Fiber no es compatible con interfaces net/http. Esto significa que no lo podrá usar en proyectos como qglgen, go-swagger, u otros que son parte del ecosistema net/http. ## 👀 Ejemplos @@ -290,7 +290,7 @@ Revise nuestro paquete para [Plantillas](https://github.com/gofiber/template) qu package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -357,8 +357,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -380,8 +380,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -465,8 +465,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -499,7 +499,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -540,8 +540,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -571,6 +571,7 @@ Aquí está una lista del middleware incluido en el marco web Fiber. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_fa.md b/.github/README_fa.md index f2e198c314..ea553ebab4 100644 --- a/.github/README_fa.md +++ b/.github/README_fa.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +


@@ -93,7 +93,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -139,7 +139,7 @@ func main() {
```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ```
@@ -161,7 +161,7 @@ go get -u github.com/gofiber/fiber/v2 - [پشتیبانی از وب سوکت](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - قابلیت [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- ترجمه در [15 زبان](https://docs.gofiber.io/) +- ترجمه در [18 زبان](https://docs.gofiber.io/) - و امکانات بیشتر, [دیدن در داکیومنت](https://docs.gofiber.io/)
@@ -182,7 +182,7 @@ Fiber از Express الهام گرفته, که محبوب ترین فری

## ⚠️ محدودیت ها -* به دلیل استفاده ناامن از Fiber, ممکن است کتابخانه همیشه با آخرین نسخه Go سازگار نباشد. Fiber 2.29.0 با زبان گو نسخه ۱.۱۴ تا ۱.۱۷ تست شده است. +* به دلیل استفاده ناامن از Fiber, ممکن است کتابخانه همیشه با آخرین نسخه Go سازگار نباشد. Fiber 2.40.0 با زبان گو نسخه 1.16 تا 1.19 تست شده است. * فریمورک Fiber با پکیج net/http سازگار نیست. این بدان معناست شما نمی توانید از پکیج های مانند go-swagger, gqlgen یا سایر پروژه هایی که بخشی از اکوسیستم net/http هستند استفاده کنید.
@@ -364,7 +364,7 @@ Fiber زمانیکه view engine تنظیم نشده باشد بطور پیش ف package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -439,8 +439,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -466,8 +466,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -567,8 +567,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -601,7 +601,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -646,8 +646,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -676,30 +676,31 @@ func main() {
-
+
| Middleware | توضیحات | | :------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Basic auth middleware provides an HTTP basic authentication. It calls the next handler for valid credentials and 401 Unauthorized for missing or invalid credentials. | -| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Intercept and cache responses | -| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Compression middleware for Fiber, it supports `deflate`, `gzip` and `brotli` by default. | -| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | -| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | -| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | -| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | -| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | -| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | -| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | FileSystem middleware for Fiber, special thanks and credits to Alireza Salary | -| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Rate-limiting middleware for Fiber. Use to limit repeated requests to public APIs and/or endpoints such as password reset. | -| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP request/response logger. | -| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Monitor middleware that reports server metrics, inspired by express-status-monitor | -| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Special thanks to Matthew Lee \(@mthli\) | -| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Allows you to proxy requests to a multiple servers | -| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Recover middleware recovers from panics anywhere in the stack chain and handles the control to the centralized[ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | -| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | Adds a requestid to every request. | -| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session middleware. NOTE: This middleware uses our Storage package. | -| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip middleware that skips a wrapped handler is a predicate is true. | -| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | Adds a max time for a request and forwards to ErrorHandler if it is exceeded. | +| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) |یک میدلور پایه که سیستم احراز هویت پایه ای را فراهم میکند. در صورت معتبر بودن درخواست روتر بعدی صدا زده شده و در صورت نامعتبر بودن خطای ۴۰۱ نمایش داده میشود.| +| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) |پاسخ هارا رهگیری کرده و انها را به صورت موقت ذخیره میکند.| +| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | یک میدلور فشرده سازی برای Fiber که به طور پیشفرض از `deflate`, `gzip` و `brotli`. پشتیبانی میکند.| | +| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) |فعال سازی هدر های cross-origin با گزینه های مختلف.| +| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) |در برابر حملات CSRF ایمنی ایجاد میکند.| +| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) |مقادیر کوکی هارا رمزنگاری میکند.| +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | با ارائه تنظیمات اختیاری، متغیرهای محیط را در معرض دید قرار دهید. | +| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | میدلور ETag به کش ها اجازه میدهد کارآمد تر عمل کرده و در پهنای باند صرفه جویی کنند. به عنوان یک وب سرور نیازی به دادن پاسخ کامل نیست اگر محتوا تغییر نکرده باشد. | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | میدلور Expvar میتواند متغیر هایی را تعریف کرده و مقادیر انها را در زمان اجرا با فرمت JSON به شما نشان دهد. | +| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | جلوگیری و یا کش کردن درخواست های favicon در صورتی که مسیر یک فایل را داده باشید.| +| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | میدلور FileSystem به شما اجازه میدهد فایل های یک مسیر را عمومی کنید. | +| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) |میدلور محدود کننده تعداد درخواست برای Fiber.| +| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) |لاگ گرفتن از درخواست و پاسخ های HTTP.| +| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) |وضعیت سرور را مانیتور و گزارش میکند، از express-status-monitor الهام گرفته شده است.| +| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | تشکر ویژه از Matthew Lee \(@mthli\)| +| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | اجازه میدهد درخواست هارا بر روی چند سرور پروکسی کنید. | +| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) |خطا های زمان اجرا را در وب سرور HTTP شما مدیریت میکنند[ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | +| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | به تمامی درخواست ها شناسه ای را اختصاص میدهد.| +| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) |برای ذخیره و مدیریت شناسه کاربری یا session بازدید کنندگان استفاده .میشود| +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) |این میدلور میتواند با استفاده از شرط های تعیین شده درخواست هایی را نادیده بگیرد.| +| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) |این میدلور محدودیت زمانی ای را برای درخواست ها تنظیم میکند، در صورتی که محدودیت به پایان برسد ErrorHandler صدا زده میشود.|


diff --git a/.github/README_fr.md b/.github/README_fr.md index f0aa43367d..093b77b6eb 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Features @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Available in [15 languages](https://docs.gofiber.io/) +- Available in [18 languages](https://docs.gofiber.io/) - Et plus encore, [explorez Fiber](https://docs.gofiber.io/) ## 💡 Philosophie @@ -142,7 +142,7 @@ Les nouveaux gophers qui passent de [Node.js](https://nodejs.org/en/about/) à [ Fiber est **inspiré** par Express, le framework web le plus populaire d'Internet. Nous avons combiné la **facilité** d'Express, et la **performance brute** de Go. Si vous avez déja développé une application web en Node.js (_en utilisant Express ou équivalent_), alors de nombreuses méthodes et principes vous sembleront **familiers**. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Exemples @@ -292,7 +292,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -359,8 +359,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -382,8 +382,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -467,8 +467,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -501,7 +501,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -542,8 +542,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -573,6 +573,7 @@ Here is a list of middleware that are included within the Fiber framework. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_he.md b/.github/README_he.md index 2f94392010..4caf140577 100644 --- a/.github/README_he.md +++ b/.github/README_he.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -96,7 +96,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -135,7 +135,7 @@ Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 יכולות @@ -187,7 +187,7 @@ Fiber נוצרה **בהשראת** Express, ה-web framework הפופולרית

## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 דוגמאות @@ -360,7 +360,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -435,8 +435,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -462,8 +462,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -563,8 +563,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -601,7 +601,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -646,8 +646,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -689,6 +689,7 @@ Here is a list of middleware that are included within the Fiber framework. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_id.md b/.github/README_id.md index 383fd970fc..80ffd9f6eb 100644 --- a/.github/README_id.md +++ b/.github/README_id.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Pastikan kamu sudah menginstalasi Golang ([unduh](https://go.dev/dl/)). Dengan v Inisialisasi proyek kamu dengan membuat folder lalu jalankan `go mod init github.com/nama-kamu/repo` ([belajar lebih banyak](https://go.dev/blog/using-go-modules)) di dalam folder. Kemudian instal Fiber dengan perintah [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Fitur @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [Mendukung WebSocket](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Tersedia dalam [15 bahasa](https://docs.gofiber.io/) +- Tersedia dalam [18 bahasa](https://docs.gofiber.io/) - Dan masih banyak lagi, [kunjungi Fiber](https://docs.gofiber.io/) ## 💡 Filosofi @@ -145,7 +145,7 @@ Kami **mendengarkan** para pengguna di [GitHub Issues](https://github.com/gofibe ## ⚠️ Limitasi -* Karena penggunaan Fiber yang tidak aman, perpustakaan mungkin tidak selalu kompatibel dengan versi Go terbaru. Fiber 2.29.0 telah diuji dengan Go versi 1.14 hingga 1.18. +* Karena penggunaan Fiber yang tidak aman, perpustakaan mungkin tidak selalu kompatibel dengan versi Go terbaru. Fiber 2.40.0 telah diuji dengan Go versi 1.16 hingga 1.19. * Fiber tidak kompatibel dengan antarmuka net/http. Ini berarti kamu tidak akan dapat menggunakan proyek seperti gqlgen, go-swagger, atau lainnya yang merupakan bagian dari ekosistem net/http. ## 👀 Contoh @@ -293,7 +293,7 @@ Lihat paket [contoh](https://github.com/gofiber/template) kami yang mendukung be package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -360,8 +360,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -383,8 +383,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -468,8 +468,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -502,7 +502,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -543,8 +543,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -574,6 +574,7 @@ Kumpulan `middleware` yang ada didalam kerangka kerja Fiber. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | @@ -607,7 +608,7 @@ Kumpulan `middleware` yang dihost external dan diurus oleh [Tim Fiber](https://g ## 🕶️ Awesome List -For more articles, middlewares, examples or tools check our [awesome list](https://github.com/gofiber/awesome-fiber). +Untuk artikel lainnya, middlewares, contoh atau tools check kami [awesome list](https://github.com/gofiber/awesome-fiber). ## 👍 Berkontribusi @@ -620,7 +621,7 @@ Apabila anda ingin mengucapkan **terima kasih** dan/atau mendukung pengembangan ## ☕ Pendukung -Fiber adalah proyek sumber terbuka yang beroperasi dalam donasi untuk membayar tagihan, seperti nama domain, _gitbook, netlify_ dan _serverless hosting_. Jika anda mau mendukung Fiber, anda dapat [**membeli kami kopi disini**](https://buymeacoff.ee/fenny). +Fiber adalah proyek sumber terbuka yang beroperasi dalam donasi untuk membayar tagihan, seperti nama domain, _gitbook, netlify_ dan _serverless hosting_. Jika anda mau mendukung Fiber, anda dapat [**membelikan kami kopi disini**](https://buymeacoff.ee/fenny). | | User | Donation | | :--------------------------------------------------------- | :----------------------------------------------- | :------- | diff --git a/.github/README_it.md b/.github/README_it.md index a9bf3819ab..50fce76ff4 100644 --- a/.github/README_it.md +++ b/.github/README_it.md @@ -5,89 +5,89 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

Fiber è un framework web inspirato a Express costruito sopra Fasthttp, un motore HTTP molto veloce per Go. Progettato per semplificare le cose per uno sviluppo veloce con zero allocazione di memoria e le prestazioni in mente.

-## ⚡️ Avvio rapido +## ⚡️ Inizia velocemente ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -111,12 +111,12 @@ Questi test sono stati eseguiti da [TechEmpower](https://www.techempower.com/ben ## ⚙️ Installazione -Assicurati di avere Go ([download](https://go.dev/dl/)) installato. Versione `1.14` o superiore. +Assicurati di avere Go ([per scaricalro](https://go.dev/dl/)) installato. Devi avere la versione `1.14` o superiore. -Inizializza il tuo progetto creando una cartella e successivamente lanciando `go mod init github.com/your/repo` ([per maggiori info](https://go.dev/blog/using-go-modules)) da dentro la cartella. Dopo installa Fiber con il comando [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): +Inizializza il tuo progetto creando una cartella e successivamente usando il comando `go mod init github.com/la-tua/repo` ([per maggiori informazioni](https://go.dev/blog/using-go-modules)) dentro la cartella. Dopodiche installa Fiber con il comando [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Caratteristiche @@ -132,23 +132,23 @@ go get -u github.com/gofiber/fiber/v2 - [Supporto WebSocket](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Disponible in [15 lingue](https://docs.gofiber.io/) +- Disponible in [18 lingue](https://docs.gofiber.io/) - E molto altro ancora, [esplora Fiber](https://docs.gofiber.io/) ## 💡 Filosofia I nuovi gopher che passano da [Node.js](https://nodejs.org/en/about/) a [Go](https://go.dev/doc/) hanno a che fare con una curva di apprendimento prima di poter iniziare a creare le proprie applicazioni web o microservizi. Fiber, come **web framework** , è stato creato con l'idea di **minimalismo** e seguendo lo '**UNIX way**' , così i nuovi gopher posso entrare rapidamente nel mondo di Go con un caldo e fidato benvenuto. -Fiber è **inspirato** da Express, il web framework più popolare su internet. Abbiamo combiniamo la **facilità** di Express e **le pure prestazioni** di Go. Se hai mai implementato una applicazione web in Node.js (_utilizzando Express o simili_), allora i tanti metodi e principi ti saranno **molto familiari**. +Fiber è **ispirato** da Express, il web framework più popolare su internet. Abbiamo combiniamo la **facilità** di Express e **le prestazioni** di Go. Se hai mai implementato una applicazione web in Node.js (_utilizzando Express o simili_), allora i tanti metodi e principi ti saranno **molto familiari**. ## ⚠️ Limitazioni -* Dato che Fiber utilizza unsafe, la libreria non sempre potrebbe essere compatibile con l'ultima versione di Go. Fiber 2.29.0 è stato testato con la versioni 1.14 alla 1.18 di Go. +* Dato che Fiber utilizza unsafe, la libreria non sempre potrebbe essere compatibile con l'ultima versione di Go. Fiber 2.40.0 è stato testato con la versioni 1.16 alla 1.19 di Go. * Fiber non è compatibile con le interfacce net/http. Questo significa che non è possibile utilizzare progetti come qglgen, go-swagger, o altri che fanno parte dell'ecosistema net/http. ## 👀 Esempi -Qui sotto trovi molti dei più comuni esempi. Se vuoi vedere ulteriori esempi, visita il nostro [Recipes repository](https://github.com/gofiber/recipes) o la nostra [documentazione API](https://docs.gofiber.io) . +Qui sotto trovi molti dei più comuni esempi. Se vuoi vedere ulteriori esempi, visita il nostro [repository delle ricette](https://github.com/gofiber/recipes) o la nostra [documentazione API](https://docs.gofiber.io) . #### 📖 [**Routing di base**](https://docs.gofiber.io/#basic-routing) @@ -191,7 +191,7 @@ func main() { ``` -#### 📖 [**Route Naming**](https://docs.gofiber.io/api/app#name) +#### 📖 [**Dare nomi alle Route**](https://docs.gofiber.io/api/app#name) ```go func main() { @@ -275,23 +275,23 @@ func main() {
📚 Mostra altri esempi -### View engine +### Motori di template -📖 [Config](https://docs.gofiber.io/api/fiber#config) -📖 [Engines](https://github.com/gofiber/template) +📖 [Configurazione](https://docs.gofiber.io/api/fiber#config) +📖 [Motori](https://github.com/gofiber/template) 📖 [Render](https://docs.gofiber.io/api/ctx#render) -Fiber usa di default [html/template](https://pkg.go.dev/html/template/) quando nessun view engine è stato impostato. +Fiber usa di default [html/template](https://pkg.go.dev/html/template/) quando nessun motore template è stato impostato. -Se vuoi eseguire parzialmente o utilizzare un engine differente come [amber](https://github.com/eknkc/amber), [handlebars](https://github.com/aymerick/raymond), [mustache](https://github.com/cbroglie/mustache) o [pug](https://github.com/Joker/jade) ecc.. +Se vuoi eseguire parzialmente o utilizzare un motore differente come [amber](https://github.com/eknkc/amber), [handlebars](https://github.com/aymerick/raymond), [mustache](https://github.com/cbroglie/mustache) o [pug](https://github.com/Joker/jade) ecc.. -Dai un'occhiata al pacchetto [Template](https://github.com/gofiber/template) che supporta multipli view engine. +Dai un'occhiata al pacchetto [Template](https://github.com/gofiber/template) che supporta multipli motore template. ```go package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -315,7 +315,7 @@ func main() { ### Raggruppare le route -📖 [Group](https://docs.gofiber.io/api/app#group) +📖 [Gruppi](https://docs.gofiber.io/api/app#group) ```go func middleware(c fiber.Ctx) error { @@ -358,8 +358,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -381,8 +381,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -460,14 +460,14 @@ func main() { } ``` -### WebSocket Upgrade +### WebSocket 📖 [Websocket](https://github.com/gofiber/websocket) ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -494,13 +494,13 @@ func main() { } ``` -### Server-Sent Events +### Eventi dal server 📖 [More Info](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -535,14 +535,14 @@ func main() { } ``` -### Recover middleware +### Recupera middleware 📖 [Recover](https://docs.gofiber.io/api/middleware/recover) ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -560,14 +560,14 @@ func main() {
-### Utilizzare Trusted Proxy +### Utilizzare i Proxy fidati 📖 [Config](https://docs.gofiber.io/api/fiber#config) ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -589,28 +589,29 @@ func main() { Qui una lista dei middleware inclusi con Fiber. -| Middleware | Descrizione | -| :------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Basic auth middleware provides an HTTP basic authentication. It calls the next handler for valid credentials and 401 Unauthorized for missing or invalid credentials. | -| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Intercept and cache responses | -| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Compression middleware for Fiber, it supports `deflate`, `gzip` and `brotli` by default. | -| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | -| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | -| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | -| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | -| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | -| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | -| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | FileSystem middleware for Fiber, special thanks and credits to Alireza Salary | -| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Rate-limiting middleware for Fiber. Use to limit repeated requests to public APIs and/or endpoints such as password reset. | -| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP request/response logger. | -| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Monitor middleware that reports server metrics, inspired by express-status-monitor | -| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Special thanks to Matthew Lee \(@mthli\) | -| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Allows you to proxy requests to a multiple servers | -| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Recover middleware recovers from panics anywhere in the stack chain and handles the control to the centralized[ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | -| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | Adds a requestid to every request. | -| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session middleware. NOTE: This middleware uses our Storage package. | -| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip middleware that skips a wrapped handler is a predicate is true. | -| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | Adds a max time for a request and forwards to ErrorHandler if it is exceeded. | +| Middleware | Descrizione | +| :------------------------------------------------------------------------------------- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Middleware basico di autenticazione usando http. Chiama il suo handler se le credenziali sono guiste e il codice 401 Unauthorized per credenziali mancanti o invailde. | +| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Intercetta e mette nella cache la risposta | +| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Middlewere di compressione per Fiber, supporta `deflate`, `gzip` e `brotli` di default. | +| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Ti permette di usare cross-origin resource sharing \(CORS\) con tante optzioni. | +| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Ti protegge da attachi CSRF. | +| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Middleware che encrypta i valori dei cookie. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Esporre le variabili di ambiente fornendo una configurazione facoltativa. | +| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | Middlewere che permette alle cache di essere piu efficienti e salvare banda, come un web server non deve rimandare il messagio pieno se il contenuto non è cambiato. | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Middleware che serve via il suo runtime server HTTP varianti esposte in formato JSON. | +| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignora favicon dai logs o serve dalla memoria se un filepath si dà. | +| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Middleware per il FileSystem per Fiber, grazie tante e crediti a Alireza Salary | +| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Middelwere per Rate-limiting per Fiber. Usato per limitare richieste continue agli APIs publici e/o endpoints come un password reset. | +| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | Logger HTTP per richiesta/risposta. | +| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Middleware per monitorare che riporta metriche server, ispirato da express-status-monitor | +| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Grazie tante a Matthew Lee \(@mthli\) | +| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Ti permette di fare richieste proxy a multipli server. | +| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Middleware per recuperare dai attachi di panico da tutte le parti nella stack chain e da il controllo al centralizzato[ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | +| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | Aggiunge un requestid a ogni richiesta. | +| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Middelwere per sessioni. NOTA: Questo middleware usa il nostro Storage package. | +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Middleware che salta un wrapped handler se un predicate è vero. | +| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | Aggiunge un tempo massimo per una richiesta e lo manda a ErrorHandler se si supera. | ## 🧬 Middleware Esterni @@ -618,26 +619,26 @@ La lista dei moduli middleware hostati esternamente e mantenuti dal [team di Fib | Middleware | Descrizione | | :------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [adaptor](https://github.com/gofiber/adaptor) | Converter for net/http handlers to/from Fiber request handlers, special thanks to @arsmn! | -| [helmet](https://github.com/gofiber/helmet) | Helps secure your apps by setting various HTTP headers. | -| [jwt](https://github.com/gofiber/jwt) | JWT returns a JSON Web Token \(JWT\) auth middleware. | -| [keyauth](https://github.com/gofiber/keyauth) | Key auth middleware provides a key based authentication. | -| [redirect](https://github.com/gofiber/redirect) | Redirect middleware | -| [rewrite](https://github.com/gofiber/rewrite) | Rewrite middleware rewrites the URL path based on provided rules. It can be helpful for backward compatibility or just creating cleaner and more descriptive links. | -| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. | -| [template](https://github.com/gofiber/template) | This package contains 8 template engines that can be used with Fiber `v1.10.x` Go version 1.13 or higher is required. | -| [websocket](https://github.com/gofiber/websocket) | Based on Fasthttp WebSocket for Fiber with Locals support! | +| [adaptor](https://github.com/gofiber/adaptor) | Converte gli handler net/http a/da i request handlers di Fiber, grazie tante a @arsmn! | +| [helmet](https://github.com/gofiber/helmet) | Aiuta a mettere sicurezza alla tua app usando vari header HTTP. | +| [jwt](https://github.com/gofiber/jwt) | Usa JSON Web Token \(JWT\) auth. | +| [keyauth](https://github.com/gofiber/keyauth) | Usa auth basato su chiavi. | +| [redirect](https://github.com/gofiber/redirect) | Middleware per reinderizzare | +| [rewrite](https://github.com/gofiber/rewrite) | Riscrive la path all URL con le regole date. Puo essere di aiuto per compatibilita o per creare link puliti e piu descrittivi. | +| [storage](https://github.com/gofiber/storage) | Dirver di storage che implementa la interfaccia Storage, fatto per essere usato con vari Fiber middleware. | +| [template](https://github.com/gofiber/template) | Questo pachetto contiene 8 motori template che possono essere usati con Fiber `v1.10.x`. Versione di go neccesaria: 1.13+. | +| [websocket](https://github.com/gofiber/websocket) | Basato su Fasthttp WebSocket per Fiber con supporto per Locals! | ## 🕶️ Awesome List -For more articles, middlewares, examples or tools check our [awesome list](https://github.com/gofiber/awesome-fiber). +Per piu articoli, middlewares, esempi o attrezzi puoi usare la [awesome list](https://github.com/gofiber/awesome-fiber). ## 👍 Contribuire Se vuoi dirci **grazie** e/o supportare lo sviluppo di `Fiber`: 1. Aggiungi una [stella GitHub](https://github.com/gofiber/fiber/stargazers) al progetto. -2. Tweeta del progetto [su Twitter](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber). +2. Twitta del progetto [su Twitter](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber). 3. Scrivi una recensione o un tutorial su [Medium](https://medium.com/), [Dev.to](https://dev.to/) o sul tuo blog personale. 4. Supporta il progetto donando una [tazza di caffè](https://buymeacoff.ee/fenny). @@ -666,7 +667,7 @@ Fiber è un progetto open source che va avanti grazie alle donazioni per pagare | ![](https://avatars.githubusercontent.com/u/31022056?s=25) | [@marvinjwendt](https://github.com/marvinjwendt) | ☕ x 1 | | ![](https://avatars.githubusercontent.com/u/31921460?s=25) | [@toishy](https://github.com/toishy) | ☕ x 1 | -## ‎‍💻 Code Contributor +## ‎‍💻 Contributori Code Contributors diff --git a/.github/README_ja.md b/.github/README_ja.md index ecaab43364..a5074cbeb6 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -5,81 +5,81 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

-FIberは、Expressに触発されたWebフレームワークです。Go 最速のHTTPエンジンであるFasthttpで作られています。ゼロメモリアロケーションパフォーマンスを念頭に置いて設計されており、迅速な開発をサポートします。 +Fiberは、Expressに触発されたWebフレームワークです。Go 最速のHTTPエンジンであるFasthttpで作られています。ゼロメモリアロケーションパフォーマンスを念頭に置いて設計されており、迅速な開発をサポートします。

@@ -88,7 +88,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -114,11 +114,10 @@ func main() { Go がインストールされていることを確認してください ([ダウンロード](https://go.dev/dl/)). バージョン `1.14` またはそれ以上であることが必要です。 -Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: フォルダを作成し、フォルダ内で `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) を実行してプロジェクトを初期化してください。その後、 Fiber を以下の [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) コマンドでインストールしてください。 ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 機能 @@ -127,14 +126,14 @@ go get -u github.com/gofiber/fiber/v2 - [静的ファイル](https://docs.gofiber.io/api/app#static)のサポート - 究極の[パフォーマンス](https://docs.gofiber.io/extra/benchmarks) - [低メモリ](https://docs.gofiber.io/extra/benchmarks)フットプリント -- Express [API エンドポイント](https://docs.gofiber.io/api/ctx) -- Middleware と[Next](https://docs.gofiber.io/api/ctx#next)のサポート +- [API エンドポイント](https://docs.gofiber.io/api/ctx) +- [Middleware](https://docs.gofiber.io/api/middleware) と[Next](https://docs.gofiber.io/api/ctx#next)のサポート - [迅速](https://dev.to/koddr/welcome-to-fiber-an-express-js-styled-fastest-web-framework-written-with-on-golang-497)なサーバーサイドプログラミング - [Template engines](https://github.com/gofiber/template) - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- [15 ヶ国語](https://docs.gofiber.io/)で利用可能 +- [18 ヶ国語](https://docs.gofiber.io/)に翻訳 - [Fiber](https://docs.gofiber.io/)をもっと知る ## 💡 哲学 @@ -146,9 +145,10 @@ Fiber は人気の高い Web フレームワークである Expressjs に**イ わたしたちは Express の**手軽さ**と Go の**パフォーマンス**を組み合わせました。 もしも、Web アプリケーションを Express 等の Node.js フレームワークで実装した経験があれば、多くの方法や原理がとても**馴染み深い**でしょう。 -## ⚠️ 制限時効 -* Fiberはunsafeパッケージを使用しているため、最新のGoバージョンと互換性がない場合があります。Fiber 2.29.0 は、Go のバージョン 1.14 から 1.18 でテストされています。 -* Fiberはnet/httpインターフェースと互換性がありません。つまり、gqlgenやgo-swaggerなど、net/httpのエコシステムの一部であるプロジェクトを使用することができません。 +## ⚠️ 制限事項 + +- Fiber は unsafe パッケージを使用しているため、最新の Go バージョンと互換性がない場合があります。Fiber 2.40.0 は、Go のバージョン 1.16 から 1.19 でテストされています。 +- Fiber は net/http インターフェースと互換性がありません。つまり、gqlgen や go-swagger など、net/http のエコシステムの一部であるプロジェクトを使用することができません。 ## 👀 例 @@ -195,7 +195,7 @@ func main() { ``` -#### 📖 [**Route Naming**](https://docs.gofiber.io/api/app#name) +#### 📖 [**ルートの命名**](https://docs.gofiber.io/api/app#name) ```go func main() { @@ -285,17 +285,17 @@ func main() { 📖 [Engines](https://github.com/gofiber/template) 📖 [Render](https://docs.gofiber.io/api/ctx#render) -view engineが設定されていない時は、Fiberのデフォルトは[html/template](https://pkg.go.dev/html/template/) になります。 +view engine が設定されていない時は、Fiber のデフォルトは[html/template](https://pkg.go.dev/html/template/) になります。 パーシャルを実行したい場合や、[amber](https://github.com/eknkc/amber), [handlebars](https://github.com/aymerick/raymond), [mustache](https://github.com/cbroglie/mustache) ,[pug](https://github.com/Joker/jade) などの別のエンジンを使用したい場合など、 -複数のview engineをサポートする [Template](https://github.com/gofiber/template) パッケージをご覧ください。 +複数の view engine をサポートする [Template](https://github.com/gofiber/template) パッケージをご覧ください。 ```go package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -362,8 +362,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -385,8 +385,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -400,7 +400,7 @@ func main() { } ``` -`Origin`ヘッダーに任意のドメインを渡してCORSのチェックをします: +`Origin`ヘッダーに任意のドメインを渡して CORS のチェックをします: ```bash curl -H "Origin: http://example.com" --verbose http://localhost:3000 @@ -470,8 +470,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -504,7 +504,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -520,7 +520,7 @@ func main() { c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { fmt.Println("WRITER") var i int - + for { i++ msg := fmt.Sprintf("%d - the time is %v", i, time.Now()) @@ -545,8 +545,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -576,6 +576,7 @@ func main() { | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | @@ -609,7 +610,7 @@ func main() { ## 🕶️ Awesome List -For more articles, middlewares, examples or tools check our [awesome list](https://github.com/gofiber/awesome-fiber). +その他の記事、ミドルウェア、サンプル、ツールについては、私たちの[awesome list](https://github.com/gofiber/awesome-fiber)をご覧ください。 ## 👍 貢献する @@ -617,7 +618,7 @@ For more articles, middlewares, examples or tools check our [awesome list](https 1. [GitHub Star](https://github.com/gofiber/fiber/stargazers)をつけてください 。 2. [あなたの Twitter で](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber)プロジェクトについてツイートしてください。 -3. [Medium](https://medium.com/) 、 [Dev.to、](https://dev.to/)または個人のブログでレビューまたはチュートリアルを書いてください。 +3. [Medium](https://medium.com/) 、 [Dev.to](https://dev.to/)、または個人のブログでレビューやチュートリアルを書いてください。 4. [cup of coffee](https://buymeacoff.ee/fenny)の寄付でプロジェクトを支援しましょう。 ## ☕ サポーター diff --git a/.github/README_ko.md b/.github/README_ko.md index 944fa05441..4211623394 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Go가 설치되어 있는 것을 확인해 주세요 ([download](https://go.dev/ Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 특징 @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket support](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Available in [15 languages](https://docs.gofiber.io/) +- Available in [18 languages](https://docs.gofiber.io/) - 더 알고 싶다면, [Fiber 둘러보기](https://docs.gofiber.io/) ## 💡 철학 @@ -144,7 +144,7 @@ Fiber는 인터넷에서 가장 인기있는 웹 프레임워크인 Express에 우리는 **어떤한** 작업, **마감일정**, 개발자의 **기술**이던간에 **빠르고**, **유연하고**, **익숙한** Go 웹 프레임워크를 만들기 위해 사용자들의 [이슈들](https://github.com/gofiber/fiber/issues)을(그리고 모든 인터넷을 통해) **듣고 있습니다**! Express가 자바스크립트 세계에서 하는 것 처럼요. ## ⚠️ 한계점 -* Fiber는 unsafe 패키지를 사용하기 때문에 최신 Go버전과 호환되지 않을 수 있습니다.Fiber 2.29.0은 Go 버전 1.14에서 1.18로 테스트되고 있습니다. +* Fiber는 unsafe 패키지를 사용하기 때문에 최신 Go버전과 호환되지 않을 수 있습니다.Fiber 2.40.0은 Go 버전 1.16에서 1.19로 테스트되고 있습니다. * Fiber는 net/http 인터페이스와 호환되지 않습니다.즉, gqlgen이나 go-swagger 등 net/http 생태계의 일부인 프로젝트를 사용할 수 없습니다. ## 👀 예제 @@ -296,7 +296,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -363,8 +363,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -386,8 +386,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -471,8 +471,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -505,7 +505,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -546,8 +546,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -577,6 +577,7 @@ Fiber 프레임워크에 포함되는 미들웨어 목록입니다. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_nl.md b/.github/README_nl.md index 77af89ab1e..478a51c870 100644 --- a/.github/README_nl.md +++ b/.github/README_nl.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Features @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket ondersteuning](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/middleware/limiter) -- Vertaald in [15 talen](https://docs.gofiber.io/) +- Vertaald in [18 talen](https://docs.gofiber.io/) - En nog veel meer, [ontdek Fiber](https://docs.gofiber.io/) ## 💡 Filosofie @@ -144,7 +144,7 @@ Fiber is **geïnspireerd** door Express, het populairste webframework op interne We **luisteren** naar onze gebruikers in [issues](https://github.com/gofiber/fiber/issues) (_en overal op het internet_) om een **snelle**, **flexibele** en **vriendelijk** Go web framework te maken voor **elke** taak, **deadline** en ontwikkelaar **vaardigheid**! Net zoals Express dat doet in de JavaScript-wereld. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Voorbeelden @@ -296,7 +296,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -363,8 +363,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -386,8 +386,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -471,8 +471,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -505,7 +505,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -546,8 +546,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -577,6 +577,7 @@ Here is a list of middleware that are included within the Fiber framework. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_pt.md b/.github/README_pt.md index 40138c1eb1..5b6bae235e 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Certifique-se de ter o Go instalado ([download](https://go.dev/dl/)). Versão `1 Inicie seu projeto criando um diretório e então execute `go mod init github.com/your/repo` ([saiba mais](https://go.dev/blog/using-go-modules)) dentro dele. Então, instale o Fiber com o comando [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Recursos @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [Suporte à WebSockets](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Limitador de requisições](https://docs.gofiber.io/api/middleware/limiter) -- Disponível em [15 línguas](https://docs.gofiber.io/) +- Disponível em [18 línguas](https://docs.gofiber.io/) - E muito mais, [explore o Fiber](https://docs.gofiber.io/) ## 💡 Filosofia @@ -142,7 +142,7 @@ Os novos gophers que mudaram do [Node.js](https://nodejs.org/en/about/) para o [ O Fiber é **inspirado** no Express, o framework web mais popular da Internet. Combinamos a **facilidade** do Express e com o **desempenho bruto** do Go. Se você já implementou um aplicativo web com Node.js ( _usando Express.js ou similar_ ), então muitos métodos e princípios parecerão **muito familiares** para você. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Exemplos @@ -290,7 +290,7 @@ Se você quiser uma execução parcial ou usar uma engine diferente como [amber] package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -357,8 +357,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -380,8 +380,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -465,8 +465,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -499,7 +499,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -540,8 +540,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -571,6 +571,7 @@ Here is a list of middleware that are included within the Fiber framework. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_ru.md b/.github/README_ru.md index 84136998eb..ebbeaa1826 100644 --- a/.github/README_ru.md +++ b/.github/README_ru.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ func main() { Инициализируйте проект, создав папку, а затем запустив `go mod init github.com/your/repo` ([подробнее](https://go.dev/blog/using-go-modules)) внутри этой папки. Далее, установите Fiber с помощью команды [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Особенности @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [Поддержка WebSocket](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- Документация доступна на [15 языках](https://docs.gofiber.io/) +- Документация доступна на [18 языках](https://docs.gofiber.io/) - И многое другое, [посетите наш Wiki](https://docs.gofiber.io/) ## 💡 Философия @@ -144,7 +144,7 @@ Fiber **вдохновлен** Express, самым популярным веб Мы **прислушиваемся** к нашим пользователям в [issues](https://github.com/gofiber/fiber/issues), Discord [канале](https://gofiber.io/discord) _и в остальном Интернете_, чтобы создать **быстрый**, **гибкий** и **дружелюбный** веб фреймворк на Go для **любых** задач, **дедлайнов** и **уровней** разработчиков! Как это делает Express в мире JavaScript. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 Примеры @@ -290,7 +290,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -356,8 +356,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -380,8 +380,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -468,8 +468,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -505,7 +505,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -546,8 +546,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -578,6 +578,7 @@ func main() { | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_sa.md b/.github/README_sa.md index ceeea48184..1532d6ac60 100644 --- a/.github/README_sa.md +++ b/.github/README_sa.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -91,7 +91,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -127,7 +127,7 @@ func main() {

```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ```
@@ -145,7 +145,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket دعم](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- ترجم الى [15 لغة أخرى](https://docs.gofiber.io/) +- ترجم الى [18 لغة أخرى](https://docs.gofiber.io/) - وأكثر بكثير, [استكشف Fiber](https://docs.gofiber.io/) ## 💡 فلسفة @@ -158,7 +158,7 @@ Fiber هو **مستوحى** من Express, إطار الويب الأكثر شع ** و تطوير **مهارات**! فقط مثل Express تفعل لـ JavaScript عالم. ## ⚠️ Limitations -* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.29.0 has been tested with Go versions 1.14 to 1.18. +* Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber 2.40.0 has been tested with Go versions 1.16 to 1.19. * Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem. ## 👀 أمثلة @@ -324,7 +324,7 @@ Checkout our [Template](https://github.com/gofiber/template) package that suppor package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -399,8 +399,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -426,8 +426,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -527,8 +527,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -565,7 +565,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -610,8 +610,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -642,6 +642,7 @@ Here is a list of middleware that are included within the Fiber framework. | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Enable cross-origin resource sharing \(CORS\) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | Protect from CSRF exploits. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | diff --git a/.github/README_tr.md b/.github/README_tr.md index 5201dececf..13096ce8c9 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ Go'nun `1.14` sürümü ([indir](https://go.dev/dl/)) veya daha yüksek bir sür Bir dizin oluşturup dizinin içinde `go mod init github.com/your/repo` komutunu yazarak projenizi geliştirmeye başlayın ([daha fazla öğren](https://go.dev/blog/using-go-modules)). Ardından Fiber'ı kurmak için [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) komutunu çalıştırın: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 Özellikler @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket desteği](https://github.com/gofiber/websocket) - [Server-Sent eventler](https://github.com/gofiber/recipes/tree/master/sse) - [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter) -- [15 dilde](https://docs.gofiber.io/) mevcut +- [18 dilde](https://docs.gofiber.io/) mevcut - Ve daha fazlası, [Fiber'ı keşfet](https://docs.gofiber.io/) ## 💡 Felsefe @@ -143,7 +143,7 @@ Fiber, internet üzerinde en popüler web framework'ü olan Express'ten **esinle ## ⚠️ Sınırlamalar -- Fiber unsafe kullanımı sebebiyle Go'nun son sürümüyle her zaman uyumlu olmayabilir. Fiber 2.29.0, Go 1.14 ile 1.18 sürümleriyle test edildi. +- Fiber unsafe kullanımı sebebiyle Go'nun son sürümüyle her zaman uyumlu olmayabilir. Fiber 2.40.0, Go 1.14 ile 1.19 sürümleriyle test edildi. - Fiber net/http arabirimiyle uyumlu değildir. Yani gqlgen veya go-swagger gibi net/http ekosisteminin parçası olan projeleri kullanamazsınız. ## 👀 Örnekler @@ -291,7 +291,7 @@ Kısmi yürütmek istiyorsanız veya [amber](https://github.com/eknkc/amber), [h package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -357,8 +357,8 @@ package main import ( "fmt" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -380,8 +380,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -465,8 +465,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -499,7 +499,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -540,8 +540,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -564,26 +564,27 @@ func main() { Fiber'a dahil edilen middlewareların bir listesi aşağıda verilmiştir. | Middleware | Açıklama | -| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Basic auth middleware'i, bir HTTP Basic auth sağlar. Geçerli kimlik bilgileri için sonraki handlerı ve eksik veya geçersiz kimlik bilgileri için 401 döndürür. | -| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Reponseları durdur ve önbelleğe al | +| :------------------------------------------------------------------------------------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Basic auth middleware'ı, bir HTTP Basic auth sağlar. Geçerli kimlik bilgileri için sonraki handlerı ve eksik veya geçersiz kimlik bilgileri için 401 döndürür. | +| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Reponseları durdur ve önbelleğe al. | | [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Fiber için sıkıştırma middleware, varsayılan olarak `deflate`, `gzip` ve `brotli`yi destekler. | | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Çeşitli seçeneklerle başlangıçlar arası kaynak paylaşımını \(CORS\) etkinleştirin. | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | CSRF exploitlerinden korunun. | -| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | -| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | -| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | +| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware'ı cookie değerlerini şifreler. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Environment değişkenlerini belirtilen ayarlara göre dışarıya açar. | +| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware'ı sayfa içeriği değişmediyse bant genişliğini daha verimli kullanmak için tam sayfa içeriğini tekrar göndermez. | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware, HTTP serverinin bazı runtime değişkenlerini JSON formatında sunar. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Bir dosya yolu sağlanmışsa, loglardaki favicon'u yoksayar veya bellekten sunar. | -| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Fiber için FileSystem middleware, Alireza Salary'e özel teşekkürler | +| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Fiber için FileSystem middleware, Alireza Salary'e özel teşekkürler. | | [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Fiber için hız sınırlayıcı middleware'i. Açık API'lere ve/veya parola sıfırlama gibi endpointlere yönelik tekrarlanan istekleri sınırlamak için kullanın. | | [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP istek/yanıt logger'ı. | -| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Monitor middleware that reports server metrics, inspired by express-status-monitor | -| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Matthew Lee'ye özel teşekkürler \(@mthli\) | -| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Birden çok sunucuya proxy istekleri yapmanızı sağlar | +| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Monitor middleware'ı sunucu metriklerini rapor eder, express-status-monitor'den esinlenildi. | +| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Matthew Lee'ye özel teşekkürler \(@mthli\). | +| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Birden çok sunucuya proxy istekleri yapmanızı sağlar. | | [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Recover middleware'i, stack chain'ini herhangi bir yerindeki paniklerden kurtulur ve kontrolü merkezileştirilmiş [ErrorHandler'e](https://docs.gofiber.io/guide/error-handling) verir. | -| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | Her requeste id verir | -| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session middleware. NOTE: This middleware uses our Storage package. | -| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip middleware that skips a wrapped handler is a predicate is true. | +| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | Her requeste id verir. | +| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session için middleware. NOTE: Bu middleware Fiber'in Storage yapısını kullanır. | +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip middleware'ı verilen koşul `true` olduğunda handler'ı atlar ve işlemez. | | [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | Bir request için maksimum süre ekler ve aşılırsa ErrorHandler'a iletir. | ## 🧬 Harici Middlewarelar @@ -596,10 +597,10 @@ Harici olarak barındırılan middlewareların modüllerinin listesi. Bu middlew | [helmet](https://github.com/gofiber/helmet) | Çeşitli HTTP headerları ayarlayarak uygulamalarınızın güvenliğini sağlamaya yardımcı olur. | | [jwt](https://github.com/gofiber/jwt) | JWT, bir JSON Web Token \(JWT\) yetkilendirmesi döndüren middleware. | | [keyauth](https://github.com/gofiber/keyauth) | Key auth middleware, key tabanlı bir authentication sağlar. | -| [redirect](https://github.com/gofiber/redirect) | Redirect middleware | +| [redirect](https://github.com/gofiber/redirect) | Yönlendirme middleware 'ı. | | [rewrite](https://github.com/gofiber/rewrite) | Rewrite middleware, sağlanan kurallara göre URL yolunu yeniden yazar. Geriye dönük uyumluluk için veya yalnızca daha temiz ve daha açıklayıcı bağlantılar oluşturmak için yardımcı olabilir. | -| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. | -| [template](https://github.com/gofiber/template) | Bu paket, Fiber `v1.10.x`, Go sürüm 1.13 veya üzeri gerekli olduğunda kullanılabilecek 8 template engine içerir. | +| [storage](https://github.com/gofiber/storage) | Fiber'in Storage yapısını destekleyen birçok storage driver'ı verir. Bu sayede depolama gerektiren Fiber middlewarelarında kolaylıkla kullanılabilir. | +| [template](https://github.com/gofiber/template) | Bu paket, Fiber `v2.x.x`, Go sürüm 1.14 veya üzeri gerekli olduğunda kullanılabilecek 9 template motoru içerir. | | [websocket](https://github.com/gofiber/websocket) | Yereller desteğiyle Fiber için Fasthttp WebSocket'a dayalıdır! | ## 🕶️ Awesome Listesi diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md index c9affec9ea..fd2d20178b 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ func main() { 通过创建文件夹并在文件夹内运行 `go mod init github.com/your/repo` ([了解更多](https://go.dev/blog/using-go-modules)) 来初始化项目,然后使用 [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) 命令安装 Fiber: ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 特点 @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - [WebSocket 支持](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - [频率限制](https://docs.gofiber.io/api/middleware/limiter) -- [15 种语言](https://docs.gofiber.io/) +- [18 种语言](https://docs.gofiber.io/) - 更多请[探索文档](https://docs.gofiber.io/) ## 💡 哲学 @@ -144,7 +144,7 @@ go get -u github.com/gofiber/fiber/v2 我们会**倾听**用户在[issues](https://github.com/gofiber/fiber/issues)和 Discord [channel](https://gofiber.io/discord)和在互联网上的所有诉求,为了创建一个能让有着任何技术栈的开发者都能在deadline前完成任务的**迅速**,**灵活**以及**友好**的`Go web`框架,就像`Express`在`JavaScript`世界中一样。 ## ⚠️ 限制 -* 由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.29.0 已经在 Go 1.14 到 1.18 上测试过。 +* 由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.40.0 已经在 Go 1.16 到 1.19 上测试过。 * Fiber 与 net/http 接口不兼容。也就是说你无法使用 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。 ## 👀 示例 @@ -292,7 +292,7 @@ func main() { package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -359,8 +359,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -382,8 +382,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -467,8 +467,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -501,7 +501,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -542,8 +542,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { @@ -573,6 +573,7 @@ func main() { | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | 使用各种选项启用跨源资源共享\(CORS\). | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | 保护来自CSRF的漏洞. | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | 加密 cookie 值的加密中间件. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | 通过提供可选配置来公开环境变量。. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | 让缓存更加高效并且节省带宽,让web服务不再需要重新响应整个响应体如果响应内容未变更. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | 通过其HTTP服务器运行时间提供JSON格式的暴露变体. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路径,则忽略日志中的图标或从内存中服务. | diff --git a/.github/README_zh-TW.md b/.github/README_zh-TW.md index 74112254fd..4037f262fc 100644 --- a/.github/README_zh-TW.md +++ b/.github/README_zh-TW.md @@ -5,77 +5,77 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- - + + - - + + - + - + - + - + - +

@@ -87,7 +87,7 @@ ```go package main -import "github.com/gofiber/fiber/v2" +import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() @@ -116,7 +116,7 @@ func main() { 建立文件夾並在文件夾內執行 `go mod init github.com/your/repo` ([了解更多](https://go.dev/blog/using-go-modules)) 指令建立專案,然後使用 [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) 指令下載 fiber : ```bash -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 ``` ## 🎯 功能 @@ -132,7 +132,7 @@ go get -u github.com/gofiber/fiber/v2 - 支援[WebSocket](https://github.com/gofiber/websocket) - [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) - 支援[限速](https://docs.gofiber.io/api/middleware/limiter) -- 被翻譯成[15種語言](https://docs.gofiber.io/) +- 被翻譯成[18種語言](https://docs.gofiber.io/) - 豐富的[文件](https://docs.gofiber.io/) ## 💡 理念 @@ -144,7 +144,7 @@ Fiber **受到** 網路上最流行的 Web 框架 ExpressJS**啟發**,結合 E 有什麼問題請發[issues](https://github.com/gofiber/fiber/issues)或加入 Discord [channel](https://gofiber.io/discord)討論,我們想要創造**快速**、**彈性**、**友善**的社群給**任何人**使用!就像 Express 那樣。 ## 限制 -* 由於 Fiber 使用了 unsafe,該庫可能並不總是與最新的 Go 版本兼容。 Fiber 2.29.0 已經用 Go 版本 1.14 到 1.18 進行了測試。 +* 由於 Fiber 使用了 unsafe,該庫可能並不總是與最新的 Go 版本兼容。 Fiber 2.40.0 已經用 Go 版本 1.16 到 1.19 進行了測試。 * Fiber 與 net/http 接口不兼容。 這意味著您將無法使用 gqlgen、go-swagger 或任何其他屬於 net/http 生態系統的項目。 ## 👀 範例 @@ -292,7 +292,7 @@ func main() { package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug" ) @@ -360,8 +360,8 @@ package main import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { @@ -383,8 +383,8 @@ func main() { import ( "log" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) func main() { @@ -468,8 +468,8 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/websocket" ) func main() { @@ -502,7 +502,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) @@ -543,7 +543,7 @@ func main() { ```go import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/recover" ) @@ -573,6 +573,7 @@ func main() { | [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | 使用各種選項啟用跨域資源共享 \(CORS\)。 | | [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | 防止 CSRF 漏洞利用。 | | [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | +| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | ETag middleware that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | | [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar middleware that serves via its HTTP server runtime exposed variants in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路徑,則忽略日誌中的網站圖標或從內存中提供服務。 | diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6dcf949744..5c34c3748c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,31 @@ -**Please provide enough information so that others can review your pull request:** +## Description - +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. +Explain the *details* for making this change. What existing problem does the pull request solve? -**Explain the *details* for making this change. What existing problem does the pull request solve?** +Fixes # (issue) - +## Type of change -**Commit formatting** +Please delete options that are not relevant. -Use emojis on commit messages so it provides an easy way of identifying the purpose or intention of a commit. Check out the emoji cheatsheet here: https://gitmoji.carloscuesta.me/ \ No newline at end of file +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## Checklist: + +- [ ] For new functionalities I follow the inspiration of the express js framework and built them similar in usage +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation - https://github.com/gofiber/docs for https://docs.gofiber.io/ +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] If new dependencies exist, I have checked that they are really necessary and agreed with the maintainers/community (we want to have as few dependencies as possible) +- [ ] I tried to make my code as fast as possible with as few allocations as possible +- [ ] For new code I have written benchmarks so that they can be analyzed and improved + +## Commit formatting: + +Use emojis on commit messages so it provides an easy way of identifying the purpose or intention of a commit. Check out the emoji cheatsheet here: https://gitmoji.carloscuesta.me/ diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 41fa29a751..6df748953c 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -90,4 +90,6 @@ autolabeler: template: | $CHANGES + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + Thank you $CONTRIBUTORS for making this update possible. diff --git a/.github/testdata2/bruh.tmpl b/.github/testdata2/bruh.tmpl new file mode 100644 index 0000000000..28d75b49e7 --- /dev/null +++ b/.github/testdata2/bruh.tmpl @@ -0,0 +1 @@ +

I'm Bruh

\ No newline at end of file diff --git a/.github/testdata3/hello_world.tmpl b/.github/testdata3/hello_world.tmpl new file mode 100644 index 0000000000..d47d8c59d3 --- /dev/null +++ b/.github/testdata3/hello_world.tmpl @@ -0,0 +1 @@ +

Hello {{ .Name }}!

\ No newline at end of file diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0adeb1782b..9cc328eed5 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -7,7 +7,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.18.x + go-version: 1.19.x - name: Fetch Repository uses: actions/checkout@v3 - name: Run Benchmark @@ -16,7 +16,8 @@ jobs: uses: actions/cache@v3 with: path: ./cache - key: ${{ runner.os }}-benchmark + # TODO: reactivate it later -> when v3 is the stable one + key: ${{ runner.os }}-benchmark-v3 - name: Save Benchmark Results uses: rhysd/github-action-benchmark@v1 with: @@ -25,4 +26,5 @@ jobs: github-token: ${{ secrets.BENCHMARK_TOKEN }} fail-on-alert: true comment-on-alert: true - auto-push: true + # TODO: reactivate it later -> when v3 is the stable one + auto-push: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 125ccf2db6..1ffb54108b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: Build: strategy: matrix: - go-version: [1.19.x] + go-version: [1.18.x, 1.19.x] platform: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: @@ -35,4 +35,8 @@ jobs: - name: Fetch Repository uses: actions/checkout@v3 - name: Run Test - run: go test ./... -v -race + uses: nick-fields/retry@v2 + with: + max_attempts: 3 + timeout_minutes: 15 + command: go test ./... -v -race diff --git a/addon/retry/README.md b/addon/retry/README.md new file mode 100644 index 0000000000..a07c7637e5 --- /dev/null +++ b/addon/retry/README.md @@ -0,0 +1,97 @@ +# Retry Addon + +Retry addon for [Fiber](https://github.com/gofiber/fiber) designed to apply retry mechanism for unsuccessful network +operations. This addon uses exponential backoff algorithm with jitter. It calls the function multiple times and tries +to make it successful. If all calls are failed, then, it returns error. It adds a jitter at each retry step because adding +a jitter is a way to break synchronization across the client and avoid collision. + +## Table of Contents + +- [Retry Addon](#retry-addon) + - [Table of Contents](#table-of-contents) + - [Signatures](#signatures) + - [Examples](#examples) + - [Default Config](#default-config) + - [Custom Config](#custom-config) + - [Config](#config) + - [Default Config Example](#default-config-example) + +## Signatures + +```go +func NewExponentialBackoff(config ...Config) *ExponentialBackoff +``` + +## Examples + +Firstly, import the addon from Fiber, + +```go +import ( + "github.com/gofiber/fiber/v3/addon/retry" +) +``` + +## Default Config + +```go +retry.NewExponentialBackoff() +``` + +## Custom Config + +```go +retry.NewExponentialBackoff(retry.Config{ + InitialInterval: 2 * time.Second, + MaxBackoffTime: 64 * time.Second, + Multiplier: 2.0, + MaxRetryCount: 15, +}) +``` + +## Config + +```go +// Config defines the config for addon. +type Config struct { + // InitialInterval defines the initial time interval for backoff algorithm. + // + // Optional. Default: 1 * time.Second + InitialInterval time.Duration + + // MaxBackoffTime defines maximum time duration for backoff algorithm. When + // the algorithm is reached this time, rest of the retries will be maximum + // 32 seconds. + // + // Optional. Default: 32 * time.Second + MaxBackoffTime time.Duration + + // Multiplier defines multiplier number of the backoff algorithm. + // + // Optional. Default: 2.0 + Multiplier float64 + + // MaxRetryCount defines maximum retry count for the backoff algorithm. + // + // Optional. Default: 10 + MaxRetryCount int + + // currentInterval tracks the current waiting time. + // + // Optional. Default: 1 * time.Second + currentInterval time.Duration +} +``` + +## Default Config Example + +```go +// DefaultConfig is the default config for retry. +var DefaultConfig = Config{ + InitialInterval: 1 * time.Second, + MaxBackoffTime: 32 * time.Second, + Multiplier: 2.0, + MaxRetryCount: 10, + currentInterval: 1 * time.Second, +} +``` \ No newline at end of file diff --git a/addon/retry/config.go b/addon/retry/config.go new file mode 100644 index 0000000000..a2dcd27122 --- /dev/null +++ b/addon/retry/config.go @@ -0,0 +1,66 @@ +package retry + +import "time" + +// Config defines the config for addon. +type Config struct { + // InitialInterval defines the initial time interval for backoff algorithm. + // + // Optional. Default: 1 * time.Second + InitialInterval time.Duration + + // MaxBackoffTime defines maximum time duration for backoff algorithm. When + // the algorithm is reached this time, rest of the retries will be maximum + // 32 seconds. + // + // Optional. Default: 32 * time.Second + MaxBackoffTime time.Duration + + // Multiplier defines multiplier number of the backoff algorithm. + // + // Optional. Default: 2.0 + Multiplier float64 + + // MaxRetryCount defines maximum retry count for the backoff algorithm. + // + // Optional. Default: 10 + MaxRetryCount int + + // currentInterval tracks the current waiting time. + // + // Optional. Default: 1 * time.Second + currentInterval time.Duration +} + +// DefaultConfig is the default config for retry. +var DefaultConfig = Config{ + InitialInterval: 1 * time.Second, + MaxBackoffTime: 32 * time.Second, + Multiplier: 2.0, + MaxRetryCount: 10, + currentInterval: 1 * time.Second, +} + +// configDefault sets the config values if they are not set. +func configDefault(config ...Config) Config { + if len(config) == 0 { + return DefaultConfig + } + cfg := config[0] + if cfg.InitialInterval == 0 { + cfg.InitialInterval = DefaultConfig.InitialInterval + } + if cfg.MaxBackoffTime == 0 { + cfg.MaxBackoffTime = DefaultConfig.MaxBackoffTime + } + if cfg.Multiplier <= 0 { + cfg.Multiplier = DefaultConfig.Multiplier + } + if cfg.MaxRetryCount <= 0 { + cfg.MaxRetryCount = DefaultConfig.MaxRetryCount + } + if cfg.currentInterval != cfg.InitialInterval { + cfg.currentInterval = DefaultConfig.currentInterval + } + return cfg +} diff --git a/addon/retry/exponential_backoff.go b/addon/retry/exponential_backoff.go new file mode 100644 index 0000000000..6740c35971 --- /dev/null +++ b/addon/retry/exponential_backoff.go @@ -0,0 +1,73 @@ +package retry + +import ( + "crypto/rand" + "math/big" + "time" +) + +// ExponentialBackoff is a retry mechanism for retrying some calls. +type ExponentialBackoff struct { + // InitialInterval is the initial time interval for backoff algorithm. + InitialInterval time.Duration + + // MaxBackoffTime is the maximum time duration for backoff algorithm. It limits + // the maximum sleep time. + MaxBackoffTime time.Duration + + // Multiplier is a multiplier number of the backoff algorithm. + Multiplier float64 + + // MaxRetryCount is the maximum number of retry count. + MaxRetryCount int + + // currentInterval tracks the current sleep time. + currentInterval time.Duration +} + +// NewExponentialBackoff creates a ExponentialBackoff with default values. +func NewExponentialBackoff(config ...Config) *ExponentialBackoff { + cfg := configDefault(config...) + return &ExponentialBackoff{ + InitialInterval: cfg.InitialInterval, + MaxBackoffTime: cfg.MaxBackoffTime, + Multiplier: cfg.Multiplier, + MaxRetryCount: cfg.MaxRetryCount, + currentInterval: cfg.currentInterval, + } +} + +// Retry is the core logic of the retry mechanism. If the calling function returns +// nil as an error, then the Retry method is terminated with returning nil. Otherwise, +// if all function calls are returned error, then the method returns this error. +func (e *ExponentialBackoff) Retry(f func() error) error { + if e.currentInterval <= 0 { + e.currentInterval = e.InitialInterval + } + var err error + for i := 0; i < e.MaxRetryCount; i++ { + err = f() + if err == nil { + return nil + } + next := e.next() + time.Sleep(next) + } + return err +} + +// next calculates the next sleeping time interval. +func (e *ExponentialBackoff) next() time.Duration { + // generate a random value between [0, 1000) + n, err := rand.Int(rand.Reader, big.NewInt(1000)) + if err != nil { + return e.MaxBackoffTime + } + t := e.currentInterval + (time.Duration(n.Int64()) * time.Millisecond) + e.currentInterval = time.Duration(float64(e.currentInterval) * e.Multiplier) + if t >= e.MaxBackoffTime { + e.currentInterval = e.MaxBackoffTime + return e.MaxBackoffTime + } + return t +} diff --git a/addon/retry/exponential_backoff_test.go b/addon/retry/exponential_backoff_test.go new file mode 100644 index 0000000000..3f4edd62cd --- /dev/null +++ b/addon/retry/exponential_backoff_test.go @@ -0,0 +1,125 @@ +package retry + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestExponentialBackoff_Retry(t *testing.T) { + tests := []struct { + name string + expBackoff *ExponentialBackoff + f func() error + expErr error + }{ + { + name: "With default values - successful", + expBackoff: NewExponentialBackoff(), + f: func() error { + return nil + }, + }, + { + name: "With default values - unsuccessful", + expBackoff: NewExponentialBackoff(), + f: func() error { + return fmt.Errorf("failed function") + }, + expErr: fmt.Errorf("failed function"), + }, + { + name: "Successful function", + expBackoff: &ExponentialBackoff{ + InitialInterval: 1 * time.Millisecond, + MaxBackoffTime: 100 * time.Millisecond, + Multiplier: 2.0, + MaxRetryCount: 5, + }, + f: func() error { + return nil + }, + }, + { + name: "Unsuccessful function", + expBackoff: &ExponentialBackoff{ + InitialInterval: 2 * time.Millisecond, + MaxBackoffTime: 100 * time.Millisecond, + Multiplier: 2.0, + MaxRetryCount: 5, + }, + f: func() error { + return fmt.Errorf("failed function") + }, + expErr: fmt.Errorf("failed function"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.expBackoff.Retry(tt.f) + require.Equal(t, tt.expErr, err) + }) + } +} + +func TestExponentialBackoff_Next(t *testing.T) { + tests := []struct { + name string + expBackoff *ExponentialBackoff + expNextTimeIntervals []time.Duration + }{ + { + name: "With default values", + expBackoff: NewExponentialBackoff(), + expNextTimeIntervals: []time.Duration{ + 1 * time.Second, + 2 * time.Second, + 4 * time.Second, + 8 * time.Second, + 16 * time.Second, + 32 * time.Second, + 32 * time.Second, + 32 * time.Second, + 32 * time.Second, + 32 * time.Second, + }, + }, + { + name: "Custom values", + expBackoff: &ExponentialBackoff{ + InitialInterval: 2.0 * time.Second, + MaxBackoffTime: 64 * time.Second, + Multiplier: 3.0, + MaxRetryCount: 8, + currentInterval: 2.0 * time.Second, + }, + expNextTimeIntervals: []time.Duration{ + 2 * time.Second, + 6 * time.Second, + 18 * time.Second, + 54 * time.Second, + 64 * time.Second, + 64 * time.Second, + 64 * time.Second, + 64 * time.Second, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for i := 0; i < tt.expBackoff.MaxRetryCount; i++ { + next := tt.expBackoff.next() + if next < tt.expNextTimeIntervals[i] || next > tt.expNextTimeIntervals[i]+1*time.Second { + t.Errorf("wrong next time:\n"+ + "actual:%v\n"+ + "expected range:%v-%v\n", + next, tt.expNextTimeIntervals[i], tt.expNextTimeIntervals[i]+1*time.Second) + } + } + }) + } +} diff --git a/app.go b/app.go index 9944fb28a9..ae9a6ecabc 100644 --- a/app.go +++ b/app.go @@ -9,31 +9,20 @@ package fiber import ( "bufio" - "bytes" - "crypto/tls" - "crypto/x509" + "encoding/json" + "encoding/xml" "errors" "fmt" "net" "net/http" "net/http/httputil" - "os" - "path/filepath" "reflect" - "runtime" - "sort" "strconv" "strings" "sync" - "sync/atomic" - "text/tabwriter" "time" - "encoding/json" - - "github.com/gofiber/fiber/v3/utils" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) @@ -74,9 +63,10 @@ type Storage interface { // returned from any handlers in the stack // // cfg := fiber.Config{} -// cfg.ErrorHandler = func(c *DefaultCtx, err error) error { +// cfg.ErrorHandler = func(c Ctx, err error) error { // code := StatusInternalServerError -// if e, ok := err.(*Error); ok { +// var e *fiber.Error +// if errors.As(err, &e) { // code = e.Code // } // c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) @@ -114,10 +104,8 @@ type App struct { getBytes func(s string) (b []byte) // Converts byte slice to a string getString func(b []byte) string - // Mounted and main apps - appList map[string]*App // Hooks - hooks *hooks + hooks *Hooks // Latest route & group latestRoute *Route latestGroup *Group @@ -125,15 +113,14 @@ type App struct { newCtxFunc func(app *App) CustomCtx // custom binders customBinders []CustomBinder + // TLS handler + tlsHandler *TLSHandler + // Mount fields + mountFields *mountFields } // Config is a struct holding the server settings. type Config struct { - // When set to true, this will spawn multiple Go processes listening on the same port. - // - // Default: false - Prefork bool `json:"prefork"` - // Enables the "Server: value" HTTP header. // // Default: "" @@ -275,11 +262,6 @@ type Config struct { // Default: false DisableHeaderNormalizing bool `json:"disable_header_normalizing"` - // When set to true, it will not print out the «Fiber» ASCII art and listening address. - // - // Default: false - DisableStartupMessage bool `json:"disable_startup_message"` - // This function allows to setup app name for the app // // Default: nil @@ -330,11 +312,12 @@ type Config struct { // Default: json.Unmarshal JSONDecoder utils.JSONUnmarshal `json:"-"` - // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) - // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose. + // XMLEncoder set by an external client of Fiber it will use the provided implementation of a + // XMLMarshal // - // Default: NetworkTCP4 - Network string + // Allowing for flexibility in using another XML library for encoding + // Default: xml.Marshal + XMLEncoder utils.XMLMarshal `json:"-"` // If you find yourself behind some sort of proxy, like a load balancer, // then certain header information may be sent to you using special X-Forwarded-* headers or the Forwarded header. @@ -347,12 +330,12 @@ type Config struct { // If request ip in TrustedProxies whitelist then: // 1. c.Scheme() get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header // 2. c.IP() get value from ProxyHeader header. - // 3. c.Hostname() get value from X-Forwarded-Host header + // 3. c.Host() and c.Hostname() get value from X-Forwarded-Host header // But if request ip NOT in Trusted Proxies whitelist then: // 1. c.Scheme() WON't get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, // will return https in case when tls connection is handled by the app, of http otherwise // 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context - // 3. c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host() + // 3. c.Host() and c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host() // will be used to get the hostname. // // Default: false @@ -365,9 +348,17 @@ type Config struct { trustedProxiesMap map[string]struct{} trustedProxyRanges []*net.IPNet - // If set to true, will print all routes with their method, path and handler. + // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them. + // Also, c.IP() will return only the first valid IP rather than just the raw header + // WARNING: this has a performance cost associated with it. + // // Default: false - EnablePrintRoutes bool `json:"enable_print_routes"` + EnableIPValidation bool `json:"enable_ip_validation"` + + // You can define custom color scheme. They'll be used for startup message, route list and some middlewares. + // + // Optional. Default: DefaultColors + ColorScheme Colors `json:"color_scheme"` // If you want to validate header/form/query... automatically when to bind, you can define struct validator. // Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. @@ -411,6 +402,11 @@ type Static struct { // Optional. Default value 0. MaxAge int `json:"max_age"` + // ModifyResponse defines a function that allows you to alter the response. + // + // Optional. Default: nil + ModifyResponse Handler + // Next defines a function to skip this middleware when returned true. // // Optional. Default: nil @@ -437,7 +433,8 @@ const ( // DefaultErrorHandler that process return errors from handlers var DefaultErrorHandler = func(c Ctx, err error) error { code := StatusInternalServerError - if e, ok := err.(*Error); ok { + var e *Error + if errors.As(err, &e) { code = e.Code } c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) @@ -464,7 +461,6 @@ func New(config ...Config) *App { config: Config{}, getBytes: utils.UnsafeBytes, getString: utils.UnsafeString, - appList: make(map[string]*App), latestRoute: &Route{}, latestGroup: &Group{}, customBinders: []CustomBinder{}, @@ -480,6 +476,9 @@ func New(config ...Config) *App { // Define hooks app.hooks = newHooks(app) + // Define mountFields + app.mountFields = newMountFields(app) + // Override config if provided if len(config) > 0 { app.config = config[0] @@ -515,8 +514,8 @@ func New(config ...Config) *App { if app.config.JSONDecoder == nil { app.config.JSONDecoder = json.Unmarshal } - if app.config.Network == "" { - app.config.Network = NetworkTCP4 + if app.config.XMLEncoder == nil { + app.config.XMLEncoder = xml.Marshal } app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies)) @@ -524,8 +523,8 @@ func New(config ...Config) *App { app.handleTrustedProxy(ipAddress) } - // Init appList - app.appList[""] = app + // Override colors + app.config.ColorScheme = defaultColors(app.config.ColorScheme) // Init app app.init() @@ -561,7 +560,15 @@ func (app *App) RegisterCustomBinder(binder CustomBinder) { app.customBinders = append(app.customBinders, binder) } -// Assign name to specific route. +// You can use SetTLSHandler to use ClientHelloInfo when using TLS with Listener. +func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { + // Attach the tlsHandler to the config + app.mutex.Lock() + app.tlsHandler = tlsHandler + app.mutex.Unlock() +} + +// Name Assign name to specific route. func (app *App) Name(name string) Router { app.mutex.Lock() if strings.HasPrefix(app.latestRoute.path, app.latestGroup.Prefix) { @@ -578,7 +585,7 @@ func (app *App) Name(name string) Router { return app } -// Get route by name +// GetRoute Get route by name func (app *App) GetRoute(name string) Route { for _, routes := range app.stack { for _, route := range routes { @@ -591,6 +598,24 @@ func (app *App) GetRoute(name string) Route { return Route{} } +// GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. +func (app *App) GetRoutes(filterUseOption ...bool) []Route { + var rs []Route + var filterUse bool + if len(filterUseOption) != 0 { + filterUse = filterUseOption[0] + } + for _, routes := range app.stack { + for _, route := range routes { + if filterUse && route.use { + continue + } + rs = append(rs, *route) + } + } + return rs +} + // Use registers a middleware route that will match requests // with the provided prefix (which is optional and defaults to "/"). // @@ -607,7 +632,7 @@ func (app *App) GetRoute(name string) Route { // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... func (app *App) Use(args ...any) Router { var prefix string - var subApp *App + var subApps []*App var handlers []Handler for i := 0; i < len(args); i++ { @@ -615,7 +640,7 @@ func (app *App) Use(args ...any) Router { case string: prefix = arg case *App: - subApp = arg + subApps = append(subApps, arg) case Handler: handlers = append(handlers, arg) default: @@ -623,8 +648,10 @@ func (app *App) Use(args ...any) Router { } } - if subApp != nil { - app.mount(prefix, subApp) + if len(subApps) > 0 { + for _, subApp := range subApps { + app.mount(prefix, subApp) + } return app } @@ -635,7 +662,7 @@ func (app *App) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. func (app *App) Get(path string, handlers ...Handler) Router { - return app.Head(path, handlers...).Add(MethodGet, path, handlers...) + return app.Add(MethodGet, path, handlers...) } // Head registers a route for HEAD methods that asks for a response identical @@ -721,17 +748,11 @@ func (app *App) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (app *App) Route(prefix string, fn func(router Router), name ...string) Router { - // Create new group - group := app.Group(prefix) - if len(name) > 0 { - group.Name(name[0]) - } - - // Define routes - fn(group) +func (app *App) Route(path string) Register { + // Create new route + route := &Registering{app: app, path: path} - return group + return route } // Error makes it compatible with the `error` interface. @@ -751,156 +772,6 @@ func NewError(code int, message ...string) *Error { return err } -// Listener can be used to pass a custom listener. -func (app *App) Listener(ln net.Listener) error { - // Prefork is supported for custom listeners - if app.config.Prefork { - addr, tlsConfig := lnMetadata(app.config.Network, ln) - return app.prefork(app.config.Network, addr, tlsConfig) - } - // prepare the server for the start - app.startupProcess() - // Print startup message - if !app.config.DisableStartupMessage { - app.startupMessage(ln.Addr().String(), getTlsConfig(ln) != nil, "") - } - // Print routes - if app.config.EnablePrintRoutes { - app.printRoutesMessage() - } - // Start listening - return app.server.Serve(ln) -} - -// Listen serves HTTP requests from the given addr. -// -// app.Listen(":8080") -// app.Listen("127.0.0.1:8080") -func (app *App) Listen(addr string) error { - // Start prefork - if app.config.Prefork { - return app.prefork(app.config.Network, addr, nil) - } - // Setup listener - ln, err := net.Listen(app.config.Network, addr) - if err != nil { - return err - } - // prepare the server for the start - app.startupProcess() - // Print startup message - if !app.config.DisableStartupMessage { - app.startupMessage(ln.Addr().String(), false, "") - } - // Print routes - if app.config.EnablePrintRoutes { - app.printRoutesMessage() - } - // Start listening - return app.server.Serve(ln) -} - -// ListenTLS serves HTTPS requests from the given addr. -// certFile and keyFile are the paths to TLS certificate and key file: -// -// app.ListenTLS(":8080", "./cert.pem", "./cert.key") -func (app *App) ListenTLS(addr, certFile, keyFile string) error { - // Check for valid cert/key path - if len(certFile) == 0 || len(keyFile) == 0 { - return errors.New("tls: provide a valid cert or key path") - } - // Prefork is supported - if app.config.Prefork { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err) - } - config := &tls.Config{ - MinVersion: tls.VersionTLS12, - Certificates: []tls.Certificate{ - cert, - }, - } - return app.prefork(app.config.Network, addr, config) - } - // Setup listener - ln, err := net.Listen(app.config.Network, addr) - if err != nil { - return err - } - // prepare the server for the start - app.startupProcess() - // Print startup message - if !app.config.DisableStartupMessage { - app.startupMessage(ln.Addr().String(), true, "") - } - // Print routes - if app.config.EnablePrintRoutes { - app.printRoutesMessage() - } - // Start listening - return app.server.ServeTLS(ln, certFile, keyFile) -} - -// ListenMutualTLS serves HTTPS requests from the given addr. -// certFile, keyFile and clientCertFile are the paths to TLS certificate and key file: -// -// app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem") -func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error { - // Check for valid cert/key path - if len(certFile) == 0 || len(keyFile) == 0 { - return errors.New("tls: provide a valid cert or key path") - } - - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err) - } - - clientCACert, err := os.ReadFile(filepath.Clean(clientCertFile)) - if err != nil { - return err - } - clientCertPool := x509.NewCertPool() - clientCertPool.AppendCertsFromPEM(clientCACert) - - config := &tls.Config{ - MinVersion: tls.VersionTLS12, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: clientCertPool, - Certificates: []tls.Certificate{ - cert, - }, - } - - // Prefork is supported - if app.config.Prefork { - return app.prefork(app.config.Network, addr, config) - } - - // Setup listener - ln, err := tls.Listen(app.config.Network, addr, config) - if err != nil { - return err - } - - // prepare the server for the start - app.startupProcess() - - // Print startup message - if !app.config.DisableStartupMessage { - app.startupMessage(ln.Addr().String(), true, "") - } - - // Print routes - if app.config.EnablePrintRoutes { - app.printRoutesMessage() - } - - // Start listening - return app.server.Serve(ln) -} - // Config returns the app config as value ( read-only ). func (app *App) Config() Config { return app.config @@ -931,7 +802,8 @@ func (app *App) HandlersCount() uint32 { // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. func (app *App) Shutdown() error { if app.hooks != nil { - defer app.hooks.executeOnShutdownHooks() + // TODO: check should be defered? + app.hooks.executeOnShutdownHooks() } app.mutex.Lock() @@ -948,7 +820,7 @@ func (app *App) Server() *fasthttp.Server { } // Hooks returns the hook struct to register hooks. -func (app *App) Hooks() *hooks { +func (app *App) Hooks() *Hooks { return app.hooks } @@ -972,11 +844,6 @@ func (app *App) Test(req *http.Request, msTimeout ...int) (resp *http.Response, return nil, err } - // adding back the query from URL, since dump cleans it - dumps := bytes.Split(dump, []byte(" ")) - dumps[1] = []byte(req.URL.String()) - dump = bytes.Join(dumps, []byte(" ")) - // Create test connection conn := new(testConn) @@ -990,7 +857,15 @@ func (app *App) Test(req *http.Request, msTimeout ...int) (resp *http.Response, // Serve conn to server channel := make(chan error) go func() { + var returned bool + defer func() { + if !returned { + channel <- fmt.Errorf("runtime.Goexit() called in handler or server panic") + } + }() + channel <- app.server.ServeConn(conn) + returned = true }() // Wait for callback @@ -1078,7 +953,7 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { mountedPrefixParts int ) - for prefix, subApp := range app.appList { + for prefix, subApp := range app.mountFields.appList { if prefix != "" && strings.HasPrefix(ctx.Path(), prefix) { parts := len(strings.Split(prefix, "/")) if mountedPrefixParts <= parts { @@ -1095,35 +970,6 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { return app.config.ErrorHandler(ctx, err) } -// Mount attaches another app instance as a sub-router along a routing path. -// It's very useful to split up a large API as many independent routers and -// compose them as a single service using Mount. The fiber's error handler and -// any of the fiber's sub apps are added to the application's error handlers -// to be invoked on errors that happen within the prefix route. -func (app *App) mount(prefix string, sub *App) *App { - stack := sub.Stack() - prefix = strings.TrimRight(prefix, "/") - if prefix == "" { - prefix = "/" - } - - for m := range stack { - for r := range stack[m] { - route := app.copyRoute(stack[m][r]) - app.addRoute(route.Method, app.addPrefixToRoute(prefix, route)) - } - } - - // Support for configs of mounted-apps and sub-mounted-apps - for mountedPrefixes, subApp := range sub.appList { - app.appList[prefix+mountedPrefixes] = subApp - subApp.init() - } - - atomic.AddUint32(&app.handlersCount, sub.handlersCount) - return app -} - // serverErrorHandler is a wrapper around the application's error handler method // user for the fasthttp server configuration. It maps a set of fasthttp errors to fiber // errors before calling the application's error handler method. @@ -1160,266 +1006,17 @@ func (app *App) startupProcess() *App { } app.mutex.Lock() - app.buildTree() - app.mutex.Unlock() - return app -} - -// startupMessage prepares the startup message with the handler number, port, address and other information -func (app *App) startupMessage(addr string, tls bool, pids string) { - // ignore child processes - if IsChild() { - return - } - - const ( - cBlack = "\u001b[90m" - // cRed = "\u001b[91m" - cCyan = "\u001b[96m" - // cGreen = "\u001b[92m" - // cYellow = "\u001b[93m" - // cBlue = "\u001b[94m" - // cMagenta = "\u001b[95m" - // cWhite = "\u001b[97m" - cReset = "\u001b[0m" - ) - - value := func(s string, width int) string { - pad := width - len(s) - str := "" - for i := 0; i < pad; i++ { - str += "." - } - if s == "Disabled" { - str += " " + s - } else { - str += fmt.Sprintf(" %s%s%s", cCyan, s, cBlack) - } - return str - } - - center := func(s string, width int) string { - pad := strconv.Itoa((width - len(s)) / 2) - str := fmt.Sprintf("%"+pad+"s", " ") - str += s - str += fmt.Sprintf("%"+pad+"s", " ") - if len(str) < width { - str += " " - } - return str - } - - centerValue := func(s string, width int) string { - pad := strconv.Itoa((width - len(s)) / 2) - str := fmt.Sprintf("%"+pad+"s", " ") - str += fmt.Sprintf("%s%s%s", cCyan, s, cBlack) - str += fmt.Sprintf("%"+pad+"s", " ") - if len(str)-10 < width { - str += " " - } - return str - } - - pad := func(s string, width int) (str string) { - toAdd := width - len(s) - str += s - for i := 0; i < toAdd; i++ { - str += " " - } - return - } - - host, port := parseAddr(addr) - if host == "" { - if app.config.Network == NetworkTCP6 { - host = "[::1]" - } else { - host = "0.0.0.0" - } - } - - scheme := "http" - if tls { - scheme = "https" - } - - isPrefork := "Disabled" - if app.config.Prefork { - isPrefork = "Enabled" - } - - procs := strconv.Itoa(runtime.GOMAXPROCS(0)) - if !app.config.Prefork { - procs = "1" - } - - mainLogo := cBlack + " ┌───────────────────────────────────────────────────┐\n" - if app.config.AppName != "" { - mainLogo += " │ " + centerValue(app.config.AppName, 49) + " │\n" - } - mainLogo += " │ " + centerValue(" Fiber v"+Version, 49) + " │\n" - - if host == "0.0.0.0" { - mainLogo += - " │ " + center(fmt.Sprintf("%s://127.0.0.1:%s", scheme, port), 49) + " │\n" + - " │ " + center(fmt.Sprintf("(bound on host 0.0.0.0 and port %s)", port), 49) + " │\n" - } else { - mainLogo += - " │ " + center(fmt.Sprintf("%s://%s:%s", scheme, host, port), 49) + " │\n" - } - - mainLogo += fmt.Sprintf( - " │ │\n"+ - " │ Handlers %s Processes %s │\n"+ - " │ Prefork .%s PID ....%s │\n"+ - " └───────────────────────────────────────────────────┘"+ - cReset, - value(strconv.Itoa(int(app.handlersCount)), 14), value(procs, 12), - value(isPrefork, 14), value(strconv.Itoa(os.Getpid()), 14), - ) - - var childPidsLogo string - if app.config.Prefork { - var childPidsTemplate string - childPidsTemplate += "%s" - childPidsTemplate += " ┌───────────────────────────────────────────────────┐\n%s" - childPidsTemplate += " └───────────────────────────────────────────────────┘" - childPidsTemplate += "%s" - - newLine := " │ %s%s%s │" - - // Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs - var pidSlice []string - for _, v := range strings.Split(pids, ",") { - if v != "" { - pidSlice = append(pidSlice, v) - } - } - - var lines []string - thisLine := "Child PIDs ... " - var itemsOnThisLine []string - - addLine := func() { - lines = append(lines, - fmt.Sprintf( - newLine, - cBlack, - thisLine+cCyan+pad(strings.Join(itemsOnThisLine, ", "), 49-len(thisLine)), - cBlack, - ), - ) - } - - for _, pid := range pidSlice { - if len(thisLine+strings.Join(append(itemsOnThisLine, pid), ", ")) > 49 { - addLine() - thisLine = "" - itemsOnThisLine = []string{pid} - } else { - itemsOnThisLine = append(itemsOnThisLine, pid) - } - } - - // Add left over items to their own line - if len(itemsOnThisLine) != 0 { - addLine() - } - - // Form logo - childPidsLogo = fmt.Sprintf(childPidsTemplate, - cBlack, - strings.Join(lines, "\n")+"\n", - cReset, - ) - } - - // Combine both the child PID logo and the main Fiber logo - - // Pad the shorter logo to the length of the longer one - splitMainLogo := strings.Split(mainLogo, "\n") - splitChildPidsLogo := strings.Split(childPidsLogo, "\n") - - mainLen := len(splitMainLogo) - childLen := len(splitChildPidsLogo) - - if mainLen > childLen { - diff := mainLen - childLen - for i := 0; i < diff; i++ { - splitChildPidsLogo = append(splitChildPidsLogo, "") - } - } else { - diff := childLen - mainLen - for i := 0; i < diff; i++ { - splitMainLogo = append(splitMainLogo, "") - } - } - - // Combine the two logos, line by line - output := "\n" - for i := range splitMainLogo { - output += cBlack + splitMainLogo[i] + " " + splitChildPidsLogo[i] + "\n" - } - - out := colorable.NewColorableStdout() - if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { - out = colorable.NewNonColorable(os.Stdout) - } - - _, _ = fmt.Fprintln(out, output) -} - -// printRoutesMessage print all routes with method, path, name and handlers -// in a format of table, like this: -// method | path | name | handlers -// GET | / | routeName | github.com/gofiber/fiber/v3.emptyHandler -// HEAD | / | | github.com/gofiber/fiber/v3.emptyHandler -func (app *App) printRoutesMessage() { - // ignore child processes - if IsChild() { - return - } - - const ( - // cBlack = "\u001b[90m" - // cRed = "\u001b[91m" - cCyan = "\u001b[96m" - cGreen = "\u001b[92m" - cYellow = "\u001b[93m" - cBlue = "\u001b[94m" - // cMagenta = "\u001b[95m" - cWhite = "\u001b[97m" - // cReset = "\u001b[0m" - ) - var routes []RouteMessage - for _, routeStack := range app.stack { - for _, route := range routeStack { - var newRoute = RouteMessage{} - newRoute.name = route.Name - newRoute.method = route.Method - newRoute.path = route.Path - for _, handler := range route.Handlers { - newRoute.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " " - } - routes = append(routes, newRoute) - } - } - - out := colorable.NewColorableStdout() - if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { - out = colorable.NewNonColorable(os.Stdout) - } + defer app.mutex.Unlock() - w := tabwriter.NewWriter(out, 1, 1, 1, ' ', 0) - // Sort routes by path - sort.Slice(routes, func(i, j int) bool { - return routes[i].path < routes[j].path + // add routes of sub-apps + app.mountFields.subAppsRoutesAdded.Do(func() { + app.appendSubAppLists(app.mountFields.appList) + app.addSubAppsRoutes(app.mountFields.appList) + app.generateAppListKeys() }) - _, _ = fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\n", cBlue, cWhite, cGreen, cWhite, cCyan, cWhite, cYellow) - _, _ = fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\n", cBlue, cWhite, cGreen, cWhite, cCyan, cWhite, cYellow) - for _, route := range routes { - _, _ = fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %s%s\n", cBlue, route.method, cWhite, cGreen, route.path, cWhite, cCyan, route.name, cWhite, cYellow, route.handlers) - } - _ = w.Flush() + // build route tree stack + app.buildTree() + + return app } diff --git a/app_test.go b/app_test.go index 397b637d48..b06c8e4e92 100644 --- a/app_test.go +++ b/app_test.go @@ -10,22 +10,19 @@ import ( "errors" "fmt" "io" - "log" "mime/multipart" "net" "net/http" "net/http/httptest" - "os" "reflect" "regexp" + "runtime" "strings" - "sync" "testing" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttputil" ) var testEmptyHandler = func(c Ctx) error { @@ -38,17 +35,17 @@ func testStatus200(t *testing.T, app *App, url string, method string) { req := httptest.NewRequest(method, url, nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") } func testErrorResponse(t *testing.T, err error, resp *http.Response, expectedBodyError string) { - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 500, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 500, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, expectedBodyError, string(body), "Response body") + require.NoError(t, err) + require.Equal(t, expectedBodyError, string(body), "Response body") } func Test_App_MethodNotAllowed(t *testing.T) { @@ -63,41 +60,43 @@ func Test_App_MethodNotAllowed(t *testing.T) { app.Options("/", testEmptyHandler) resp, err := app.Test(httptest.NewRequest(MethodPost, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, "", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, "", resp.Header.Get(HeaderAllow)) resp, err = app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) resp, err = app.Test(httptest.NewRequest(MethodPatch, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) resp, err = app.Test(httptest.NewRequest(MethodPut, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "POST, OPTIONS", resp.Header.Get(HeaderAllow)) app.Get("/", testEmptyHandler) resp, err = app.Test(httptest.NewRequest(MethodTrace, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "GET, HEAD, POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "GET, POST, OPTIONS", resp.Header.Get(HeaderAllow)) resp, err = app.Test(httptest.NewRequest(MethodPatch, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "GET, HEAD, POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "GET, POST, OPTIONS", resp.Header.Get(HeaderAllow)) + + app.Head("/", testEmptyHandler) resp, err = app.Test(httptest.NewRequest(MethodPut, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 405, resp.StatusCode) - utils.AssertEqual(t, "GET, HEAD, POST, OPTIONS", resp.Header.Get(HeaderAllow)) + require.NoError(t, err) + require.Equal(t, 405, resp.StatusCode) + require.Equal(t, "GET, HEAD, POST, OPTIONS", resp.Header.Get(HeaderAllow)) } func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T) { @@ -110,8 +109,8 @@ func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T) app.Post("/", testEmptyHandler) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) g := app.Group("/with-next", func(c Ctx) error { return c.Status(404).Next() @@ -120,8 +119,8 @@ func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T) g.Post("/", testEmptyHandler) resp, err = app.Test(httptest.NewRequest(MethodGet, "/with-next", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) } func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { @@ -143,12 +142,7 @@ func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { t.Error("Expect an error at app.Test(request)") } - utils.AssertEqual( - t, - true, - expectedError.MatchString(err.Error()), - fmt.Sprintf("Has: %s, expected pattern: %s", err.Error(), expectedError.String()), - ) + require.Regexp(t, expectedError, err.Error()) } func Test_App_Errors(t *testing.T) { @@ -161,16 +155,16 @@ func Test_App_Errors(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 500, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 500, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hi, i'm an error", string(body)) + require.NoError(t, err) + require.Equal(t, "hi, i'm an error", string(body)) _, err = app.Test(httptest.NewRequest(MethodGet, "/", strings.NewReader("big body"))) if err != nil { - utils.AssertEqual(t, "body size exceeds the given limit", err.Error(), "app.Test(req)") + require.Equal(t, "body size exceeds the given limit", err.Error(), "app.Test(req)") } } @@ -186,28 +180,28 @@ func Test_App_ErrorHandler_Custom(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hi, i'm an custom error", string(body)) + require.NoError(t, err) + require.Equal(t, "hi, i'm an custom error", string(body)) } func Test_App_ErrorHandler_HandlerStack(t *testing.T) { app := New(Config{ ErrorHandler: func(c Ctx, err error) error { - utils.AssertEqual(t, "1: USE error", err.Error()) + require.Equal(t, "1: USE error", err.Error()) return DefaultErrorHandler(c, err) }, }) app.Use("/", func(c Ctx) error { err := c.Next() // call next USE - utils.AssertEqual(t, "2: USE error", err.Error()) + require.Equal(t, "2: USE error", err.Error()) return errors.New("1: USE error") }, func(c Ctx) error { err := c.Next() // call [0] GET - utils.AssertEqual(t, "0: GET error", err.Error()) + require.Equal(t, "0: GET error", err.Error()) return errors.New("2: USE error") }) app.Get("/", func(c Ctx) error { @@ -215,24 +209,24 @@ func Test_App_ErrorHandler_HandlerStack(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 500, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 500, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "1: USE error", string(body)) + require.NoError(t, err) + require.Equal(t, "1: USE error", string(body)) } func Test_App_ErrorHandler_RouteStack(t *testing.T) { app := New(Config{ ErrorHandler: func(c Ctx, err error) error { - utils.AssertEqual(t, "1: USE error", err.Error()) + require.Equal(t, "1: USE error", err.Error()) return DefaultErrorHandler(c, err) }, }) app.Use("/", func(c Ctx) error { err := c.Next() - utils.AssertEqual(t, "0: GET error", err.Error()) + require.Equal(t, "0: GET error", err.Error()) return errors.New("1: USE error") // [2] call ErrorHandler }) app.Get("/test", func(c Ctx) error { @@ -240,50 +234,12 @@ func Test_App_ErrorHandler_RouteStack(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 500, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 500, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "1: USE error", string(body)) -} - -func Test_App_ErrorHandler_GroupMount(t *testing.T) { - micro := New(Config{ - ErrorHandler: func(c Ctx, err error) error { - utils.AssertEqual(t, "0: GET error", err.Error()) - return c.Status(500).SendString("1: custom error") - }, - }) - micro.Get("/doe", func(c Ctx) error { - return errors.New("0: GET error") - }) - - app := New() - v1 := app.Group("/v1") - v1.Use("/john", micro) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) - testErrorResponse(t, err, resp, "1: custom error") -} - -func Test_App_ErrorHandler_GroupMountRootLevel(t *testing.T) { - micro := New(Config{ - ErrorHandler: func(c Ctx, err error) error { - utils.AssertEqual(t, "0: GET error", err.Error()) - return c.Status(500).SendString("1: custom error") - }, - }) - micro.Get("/john/doe", func(c Ctx) error { - return errors.New("0: GET error") - }) - - app := New() - v1 := app.Group("/v1") - v1.Use("/", micro) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) - testErrorResponse(t, err, resp, "1: custom error") + require.Equal(t, nil, err) + require.Equal(t, "1: USE error", string(body)) } func Test_App_Nested_Params(t *testing.T) { @@ -305,60 +261,44 @@ func Test_App_Nested_Params(t *testing.T) { req := httptest.NewRequest(MethodGet, "/test/john/test/doe", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") -} - -// go test -run Test_App_Mount -func Test_App_Mount(t *testing.T) { - micro := New() - micro.Get("/doe", func(c Ctx) error { - return c.SendStatus(StatusOK) - }) - - app := New() - app.Use("/john", micro) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/john/doe", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, uint32(2), app.handlersCount) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") } func Test_App_Use_Params(t *testing.T) { app := New() app.Use("/prefix/:param", func(c Ctx) error { - utils.AssertEqual(t, "john", c.Params("param")) + require.Equal(t, "john", c.Params("param")) return nil }) app.Use("/foo/:bar?", func(c Ctx) error { - utils.AssertEqual(t, "foobar", c.Params("bar", "foobar")) + require.Equal(t, "foobar", c.Params("bar", "foobar")) return nil }) app.Use("/:param/*", func(c Ctx) error { - utils.AssertEqual(t, "john", c.Params("param")) - utils.AssertEqual(t, "doe", c.Params("*")) + require.Equal(t, "john", c.Params("param")) + require.Equal(t, "doe", c.Params("*")) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/prefix/john", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/john/doe", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "use: invalid handler func()\n", fmt.Sprintf("%v", err)) + require.Equal(t, "use: invalid handler func()\n", fmt.Sprintf("%v", err)) } }() @@ -371,28 +311,28 @@ func Test_App_Use_UnescapedPath(t *testing.T) { app := New(Config{UnescapePath: true, CaseSensitive: true}) app.Use("/cRéeR/:param", func(c Ctx) error { - utils.AssertEqual(t, "/cRéeR/اختبار", c.Path()) + require.Equal(t, "/cRéeR/اختبار", c.Path()) return c.SendString(c.Params("param")) }) app.Use("/abc", func(c Ctx) error { - utils.AssertEqual(t, "/AbC", c.Path()) + require.Equal(t, "/AbC", c.Path()) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/cR%C3%A9eR/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") + require.NoError(t, err, "app.Test(req)") // check the param result - utils.AssertEqual(t, "اختبار", app.getString(body)) + require.Equal(t, "اختبار", app.getString(body)) // with lowercase letters resp, err = app.Test(httptest.NewRequest(MethodGet, "/cr%C3%A9er/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") } func Test_App_Use_CaseSensitive(t *testing.T) { @@ -404,32 +344,98 @@ func Test_App_Use_CaseSensitive(t *testing.T) { // wrong letters in the requested route -> 404 resp, err := app.Test(httptest.NewRequest(MethodGet, "/AbC", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") // right letters in the requrested route -> 200 resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") // check the detected path when the case insensitive recognition is activated app.config.CaseSensitive = false // check the case sensitive feature resp, err = app.Test(httptest.NewRequest(MethodGet, "/AbC", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") + require.NoError(t, err, "app.Test(req)") // check the detected path result - utils.AssertEqual(t, "/AbC", app.getString(body)) + require.Equal(t, "/AbC", app.getString(body)) +} + +func Test_App_Not_Use_StrictRouting(t *testing.T) { + app := New() + + app.Use("/abc", func(c Ctx) error { + return c.SendString(c.Path()) + }) + + g := app.Group("/foo") + g.Use("/", func(c Ctx) error { + return c.SendString(c.Path()) + }) + + // wrong path in the requested route -> 404 + resp, err := app.Test(httptest.NewRequest(MethodGet, "/abc/", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + // right path in the requrested route -> 200 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + // wrong path with group in the requested route -> 404 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + // right path with group in the requrested route -> 200 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo/", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") +} + +func Test_App_Use_StrictRouting(t *testing.T) { + app := New(Config{StrictRouting: true}) + + app.Get("/abc", func(c Ctx) error { + return c.SendString(c.Path()) + }) + + g := app.Group("/foo") + g.Get("/", func(c Ctx) error { + return c.SendString(c.Path()) + }) + + // wrong path in the requested route -> 404 + resp, err := app.Test(httptest.NewRequest(MethodGet, "/abc/", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") + + // right path in the requrested route -> 200 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + // wrong path with group in the requested route -> 404 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") + + // right path with group in the requrested route -> 200 + resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo/", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } func Test_App_Add_Method_Test(t *testing.T) { app := New() defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "add: invalid http method JOHN\n", fmt.Sprintf("%v", err)) + require.Equal(t, "add: invalid http method JOHN\n", fmt.Sprintf("%v", err)) } }() app.Add("JOHN", "/doe", testEmptyHandler) @@ -447,8 +453,8 @@ func Test_App_GETOnly(t *testing.T) { req := httptest.NewRequest(MethodPost, "/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusMethodNotAllowed, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code") } func Test_App_Use_Params_Group(t *testing.T) { @@ -459,14 +465,14 @@ func Test_App_Use_Params_Group(t *testing.T) { return c.Next() }) group.Get("/test", func(c Ctx) error { - utils.AssertEqual(t, "john", c.Params("param")) - utils.AssertEqual(t, "doe", c.Params("*")) + require.Equal(t, "john", c.Params("param")) + require.Equal(t, "doe", c.Params("*")) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/prefix/john/doe/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") } func Test_App_Chaining(t *testing.T) { @@ -478,13 +484,13 @@ func Test_App_Chaining(t *testing.T) { return c.SendStatus(202) }) // check handler count for registered HEAD route - utils.AssertEqual(t, 5, len(app.stack[methodInt(MethodHead)][0].Handlers), "app.Test(req)") + require.Equal(t, 5, len(app.stack[methodInt(MethodHead)][0].Handlers), "app.Test(req)") req := httptest.NewRequest(MethodPost, "/john", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 202, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 202, resp.StatusCode, "Status code") app.Get("/test", n, n, n, n, func(c Ctx) error { return c.SendStatus(203) @@ -493,8 +499,8 @@ func Test_App_Chaining(t *testing.T) { req = httptest.NewRequest(MethodGet, "/test", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 203, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 203, resp.StatusCode, "Status code") } func Test_App_Order(t *testing.T) { @@ -518,12 +524,12 @@ func Test_App_Order(t *testing.T) { req := httptest.NewRequest(MethodGet, "/test", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(body)) + require.NoError(t, err) + require.Equal(t, "123", string(body)) } func Test_App_Methods(t *testing.T) { @@ -586,12 +592,12 @@ func Test_App_Route_Naming(t *testing.T) { subGroup := jane.Group("/sub-group").Name("sub.") subGroup.Get("/done", handler).Name("done") - utils.AssertEqual(t, "post", app.GetRoute("post").Name) - utils.AssertEqual(t, "john", app.GetRoute("john").Name) - utils.AssertEqual(t, "jane.test", app.GetRoute("jane.test").Name) - utils.AssertEqual(t, "jane.trace", app.GetRoute("jane.trace").Name) - utils.AssertEqual(t, "jane.sub.done", app.GetRoute("jane.sub.done").Name) - utils.AssertEqual(t, "test", app.GetRoute("test").Name) + require.Equal(t, "post", app.GetRoute("post").Name) + require.Equal(t, "john", app.GetRoute("john").Name) + require.Equal(t, "jane.test", app.GetRoute("jane.test").Name) + require.Equal(t, "jane.trace", app.GetRoute("jane.trace").Name) + require.Equal(t, "jane.sub.done", app.GetRoute("jane.sub.done").Name) + require.Equal(t, "test", app.GetRoute("test").Name) } func Test_App_New(t *testing.T) { @@ -606,17 +612,15 @@ func Test_App_New(t *testing.T) { func Test_App_Config(t *testing.T) { app := New(Config{ - DisableStartupMessage: true, + StrictRouting: true, }) - utils.AssertEqual(t, true, app.Config().DisableStartupMessage) + require.True(t, app.Config().StrictRouting) } func Test_App_Shutdown(t *testing.T) { t.Run("success", func(t *testing.T) { - app := New(Config{ - DisableStartupMessage: true, - }) - utils.AssertEqual(t, true, app.Shutdown() == nil) + app := New() + require.True(t, app.Shutdown() == nil) }) t.Run("no server", func(t *testing.T) { @@ -638,24 +642,24 @@ func Test_App_Static_Index_Default(t *testing.T) { app.Static("test", "", Static{Index: "index.html"}) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Hello, World!")) resp, err = app.Test(httptest.NewRequest(MethodGet, "/not-found", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "Cannot GET /not-found", string(body)) + require.NoError(t, err) + require.Equal(t, "Cannot GET /not-found", string(body)) } // go test -run Test_App_Static_Index @@ -665,25 +669,25 @@ func Test_App_Static_Direct(t *testing.T) { app.Static("/", "./.github") resp, err := app.Test(httptest.NewRequest(MethodGet, "/index.html", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Hello, World!")) resp, err = app.Test(httptest.NewRequest(MethodGet, "/testdata/testRoutes.json", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMEApplicationJSON, resp.Header.Get("Content-Type")) - utils.AssertEqual(t, "", resp.Header.Get(HeaderCacheControl), "CacheControl Control") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMEApplicationJSON, resp.Header.Get("Content-Type")) + require.Equal(t, "", resp.Header.Get(HeaderCacheControl), "CacheControl Control") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "testRoutes")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "testRoutes")) } // go test -run Test_App_Static_MaxAge @@ -693,11 +697,31 @@ func Test_App_Static_MaxAge(t *testing.T) { app.Static("/", "./.github", Static{MaxAge: 100}) resp, err := app.Test(httptest.NewRequest("GET", "/index.html", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType)) - utils.AssertEqual(t, "public, max-age=100", resp.Header.Get(HeaderCacheControl), "CacheControl Control") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType)) + require.Equal(t, "public, max-age=100", resp.Header.Get(HeaderCacheControl), "CacheControl Control") +} + +// go test -run Test_App_Static_Custom_CacheControl +func Test_App_Static_Custom_CacheControl(t *testing.T) { + app := New() + + app.Static("/", "./.github", Static{ModifyResponse: func(c Ctx) error { + if strings.Contains(string(c.GetRespHeader("Content-Type")), "text/html") { + c.Response().Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") + } + return nil + }}) + + resp, err := app.Test(httptest.NewRequest("GET", "/index.html", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, "no-cache, no-store, must-revalidate", resp.Header.Get(HeaderCacheControl), "CacheControl Control") + + normal_resp, normal_err := app.Test(httptest.NewRequest("GET", "/config.yml", nil)) + require.Equal(t, nil, normal_err, "app.Test(req)") + require.Equal(t, "", normal_resp.Header.Get(HeaderCacheControl), "CacheControl Control") } // go test -run Test_App_Static_Download @@ -707,11 +731,11 @@ func Test_App_Static_Download(t *testing.T) { app.Static("/fiber.png", "./.github/testdata/fs/img/fiber.png", Static{Download: true}) resp, err := app.Test(httptest.NewRequest("GET", "/fiber.png", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, "image/png", resp.Header.Get(HeaderContentType)) - utils.AssertEqual(t, `attachment`, resp.Header.Get(HeaderContentDisposition)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, "image/png", resp.Header.Get(HeaderContentType)) + require.Equal(t, `attachment`, resp.Header.Get(HeaderContentDisposition)) } // go test -run Test_App_Static_Group @@ -727,21 +751,21 @@ func Test_App_Static_Group(t *testing.T) { req := httptest.NewRequest(MethodGet, "/v1/v2", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) - utils.AssertEqual(t, "123", resp.Header.Get("Test-Header")) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.Equal(t, "123", resp.Header.Get("Test-Header")) grp = app.Group("/v2") grp.Static("/v3*", "./.github/index.html") req = httptest.NewRequest(MethodGet, "/v2/v3/john/doe", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) } func Test_App_Static_Wildcard(t *testing.T) { @@ -751,14 +775,14 @@ func Test_App_Static_Wildcard(t *testing.T) { req := httptest.NewRequest(MethodGet, "/yesyes/john/doe", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Test file")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Test file")) } func Test_App_Static_Prefix_Wildcard(t *testing.T) { @@ -768,22 +792,22 @@ func Test_App_Static_Prefix_Wildcard(t *testing.T) { req := httptest.NewRequest(MethodGet, "/test/john/doe", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/my/nameisjohn*", "./.github/index.html") resp, err = app.Test(httptest.NewRequest(MethodGet, "/my/nameisjohn/no/its/not", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Test file")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Test file")) } func Test_App_Static_Prefix(t *testing.T) { @@ -792,28 +816,28 @@ func Test_App_Static_Prefix(t *testing.T) { req := httptest.NewRequest(MethodGet, "/john/index.html", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/prefix", "./.github/testdata") req = httptest.NewRequest(MethodGet, "/prefix/index.html", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/single", "./.github/testdata/testRoutes.json") req = httptest.NewRequest(MethodGet, "/single", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMEApplicationJSON, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMEApplicationJSON, resp.Header.Get(HeaderContentType)) } func Test_App_Static_Trailing_Slash(t *testing.T) { @@ -822,44 +846,44 @@ func Test_App_Static_Trailing_Slash(t *testing.T) { req := httptest.NewRequest(MethodGet, "/john/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/john_without_index", "./.github/testdata/fs/css") req = httptest.NewRequest(MethodGet, "/john_without_index/", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/john/", "./.github") req = httptest.NewRequest(MethodGet, "/john/", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) req = httptest.NewRequest(MethodGet, "/john", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) app.Static("/john_without_index/", "./.github/testdata/fs/css") req = httptest.NewRequest(MethodGet, "/john_without_index/", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) } func Test_App_Static_Next(t *testing.T) { @@ -879,28 +903,28 @@ func Test_App_Static_Next(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) req.Header.Set("X-Custom-Header", "skip") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "You've skipped app.Static")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "You've skipped app.Static")) }) t.Run("app.Static is not skipped: serving index.html", func(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) req.Header.Set("X-Custom-Header", "don't skip") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!")) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Hello, World!")) }) } @@ -923,57 +947,40 @@ func Test_App_Mixed_Routes_WithSameLen(t *testing.T) { // match get route req := httptest.NewRequest(MethodGet, "/foobar", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, "TestValue", resp.Header.Get("TestHeader")) - utils.AssertEqual(t, "text/html", resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, "TestValue", resp.Header.Get("TestHeader")) + require.Equal(t, "text/html", resp.Header.Get(HeaderContentType)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "FOO_BAR", string(body)) + require.NoError(t, err) + require.Equal(t, "FOO_BAR", string(body)) // match static route req = httptest.NewRequest(MethodGet, "/tesbar", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "") - utils.AssertEqual(t, "TestValue", resp.Header.Get("TestHeader")) - utils.AssertEqual(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(HeaderContentLength) == "") + require.Equal(t, "TestValue", resp.Header.Get("TestHeader")) + require.Equal(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType)) body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!"), "Response: "+string(body)) - utils.AssertEqual(t, true, strings.HasPrefix(string(body), ""), "Response: "+string(body)) + require.NoError(t, err) + require.True(t, strings.Contains(string(body), "Hello, World!"), "Response: "+string(body)) + require.True(t, strings.HasPrefix(string(body), ""), "Response: "+string(body)) } func Test_App_Group_Invalid(t *testing.T) { defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "use: invalid handler int\n", fmt.Sprintf("%v", err)) + require.Equal(t, "use: invalid handler int\n", fmt.Sprintf("%v", err)) } }() New().Group("/").Use(1) } -// go test -run Test_App_Group_Mount -func Test_App_Group_Mount(t *testing.T) { - micro := New() - micro.Get("/doe", func(c Ctx) error { - return c.SendStatus(StatusOK) - }) - - app := New() - v1 := app.Group("/v1") - v1.Use("/john", micro) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, uint32(2), app.handlersCount) -} - func Test_App_Group(t *testing.T) { dummyHandler := testEmptyHandler @@ -1023,15 +1030,15 @@ func Test_App_Group(t *testing.T) { api.Post("/", dummyHandler) resp, err := app.Test(httptest.NewRequest(MethodPost, "/test/v1/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - // utils.AssertEqual(t, "/test/v1", resp.Header.Get("Location"), "Location") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + // require.Equal(t, "/test/v1", resp.Header.Get("Location"), "Location") api.Get("/users", dummyHandler) resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/v1/UsErS", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - // utils.AssertEqual(t, "/test/v1/users", resp.Header.Get("Location"), "Location") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + // require.Equal(t, "/test/v1/users", resp.Header.Get("Location"), "Location") } func Test_App_Route(t *testing.T) { @@ -1039,48 +1046,46 @@ func Test_App_Route(t *testing.T) { app := New() - grp := app.Route("/test", func(grp Router) { - grp.Get("/", dummyHandler) - grp.Get("/:demo?", dummyHandler) - grp.Connect("/CONNECT", dummyHandler) - grp.Put("/PUT", dummyHandler) - grp.Post("/POST", dummyHandler) - grp.Delete("/DELETE", dummyHandler) - grp.Head("/HEAD", dummyHandler) - grp.Patch("/PATCH", dummyHandler) - grp.Options("/OPTIONS", dummyHandler) - grp.Trace("/TRACE", dummyHandler) - grp.All("/ALL", dummyHandler) - grp.Use(dummyHandler) - grp.Use("/USE", dummyHandler) - }) + register := app.Route("/test"). + Get(dummyHandler). + Post(dummyHandler). + Put(dummyHandler). + Delete(dummyHandler). + Connect(dummyHandler). + Options(dummyHandler). + Trace(dummyHandler). + Patch(dummyHandler) testStatus200(t, app, "/test", MethodGet) - testStatus200(t, app, "/test/john", MethodGet) - testStatus200(t, app, "/test/CONNECT", MethodConnect) - testStatus200(t, app, "/test/PUT", MethodPut) - testStatus200(t, app, "/test/POST", MethodPost) - testStatus200(t, app, "/test/DELETE", MethodDelete) - testStatus200(t, app, "/test/HEAD", MethodHead) - testStatus200(t, app, "/test/PATCH", MethodPatch) - testStatus200(t, app, "/test/OPTIONS", MethodOptions) - testStatus200(t, app, "/test/TRACE", MethodTrace) - testStatus200(t, app, "/test/ALL", MethodPost) - testStatus200(t, app, "/test/oke", MethodGet) - testStatus200(t, app, "/test/USE/oke", MethodGet) + testStatus200(t, app, "/test", MethodHead) + testStatus200(t, app, "/test", MethodPost) + testStatus200(t, app, "/test", MethodPut) + testStatus200(t, app, "/test", MethodDelete) + testStatus200(t, app, "/test", MethodConnect) + testStatus200(t, app, "/test", MethodOptions) + testStatus200(t, app, "/test", MethodTrace) + testStatus200(t, app, "/test", MethodPatch) - grp.Route("/v1", func(grp Router) { - grp.Post("/", dummyHandler) - grp.Get("/users", dummyHandler) - }) + register.Route("/v1").Get(dummyHandler).Post(dummyHandler) - resp, err := app.Test(httptest.NewRequest(MethodPost, "/test/v1/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + resp, err := app.Test(httptest.NewRequest(MethodPost, "/test/v1", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/v1", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + register.Route("/v1").Route("/v2").Route("/v3").Get(dummyHandler).Trace(dummyHandler) + + resp, err = app.Test(httptest.NewRequest(MethodTrace, "/test/v1/v2/v3", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/v1/v2/v3", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") - resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/v1/UsErS", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") } func Test_App_Deep_Group(t *testing.T) { @@ -1099,149 +1104,23 @@ func Test_App_Deep_Group(t *testing.T) { return c.SendStatus(200) }) testStatus200(t, app, "/api/v1/user/authenticate", MethodGet) - utils.AssertEqual(t, 4, runThroughCount, "Loop count") + require.Equal(t, 4, runThroughCount, "Loop count") } // go test -run Test_App_Next_Method func Test_App_Next_Method(t *testing.T) { app := New() - app.config.DisableStartupMessage = true app.Use(func(c Ctx) error { - utils.AssertEqual(t, MethodGet, c.Method()) + require.Equal(t, MethodGet, c.Method()) err := c.Next() - utils.AssertEqual(t, MethodGet, c.Method()) + require.Equal(t, MethodGet, c.Method()) return err }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") -} - -// go test -run Test_App_Listen -func Test_App_Listen(t *testing.T) { - app := New(Config{DisableStartupMessage: true}) - - utils.AssertEqual(t, false, app.Listen(":99999") == nil) - - go func() { - time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) - }() - - utils.AssertEqual(t, nil, app.Listen(":4003")) -} - -// go test -run Test_App_Listen_Prefork -func Test_App_Listen_Prefork(t *testing.T) { - testPreforkMaster = true - - app := New(Config{DisableStartupMessage: true, Prefork: true}) - - utils.AssertEqual(t, nil, app.Listen(":99999")) -} - -// go test -run Test_App_ListenTLS -func Test_App_ListenTLS(t *testing.T) { - app := New() - - // invalid port - utils.AssertEqual(t, false, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") == nil) - // missing perm/cert file - utils.AssertEqual(t, false, app.ListenTLS(":0", "", "./.github/testdata/ssl.key") == nil) - - go func() { - time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) - }() - - utils.AssertEqual(t, nil, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")) -} - -// go test -run Test_App_ListenTLS_Prefork -func Test_App_ListenTLS_Prefork(t *testing.T) { - testPreforkMaster = true - - app := New(Config{DisableStartupMessage: true, Prefork: true}) - - // invalid key file content - utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.tmpl") == nil) - - utils.AssertEqual(t, nil, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")) -} - -// go test -run Test_App_ListenMutualTLS -func Test_App_ListenMutualTLS(t *testing.T) { - app := New() - - // invalid port - utils.AssertEqual(t, false, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem") == nil) - // missing perm/cert file - utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "", "./.github/testdata/ssl.key", "") == nil) - - go func() { - time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) - }() - - utils.AssertEqual(t, nil, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem")) -} - -// go test -run Test_App_ListenMutualTLS_Prefork -func Test_App_ListenMutualTLS_Prefork(t *testing.T) { - testPreforkMaster = true - - app := New(Config{DisableStartupMessage: true, Prefork: true}) - - // invalid key file content - utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.html", "") == nil) - - utils.AssertEqual(t, nil, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem")) -} - -// go test -run Test_App_Listener -func Test_App_Listener(t *testing.T) { - app := New() - - go func() { - time.Sleep(500 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) - }() - - ln := fasthttputil.NewInmemoryListener() - utils.AssertEqual(t, nil, app.Listener(ln)) -} - -// go test -run Test_App_Listener_Prefork -func Test_App_Listener_Prefork(t *testing.T) { - testPreforkMaster = true - - app := New(Config{DisableStartupMessage: true, Prefork: true}) - - ln := fasthttputil.NewInmemoryListener() - utils.AssertEqual(t, nil, app.Listener(ln)) -} - -func Test_App_Listener_TLS_Listener(t *testing.T) { - // Create tls certificate - cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") - if err != nil { - utils.AssertEqual(t, nil, err) - } - config := &tls.Config{Certificates: []tls.Certificate{cer}} - - ln, err := tls.Listen(NetworkTCP4, ":0", config) - utils.AssertEqual(t, nil, err) - - app := New() - - go func() { - time.Sleep(time.Millisecond * 500) - utils.AssertEqual(t, nil, app.Shutdown()) - }() - - utils.AssertEqual(t, nil, app.Listener(ln)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") } // go test -v -run=^$ -bench=Benchmark_AcquireCtx -benchmem -count=4 @@ -1265,28 +1144,27 @@ func Benchmark_NewError(b *testing.B) { // go test -run Test_NewError func Test_NewError(t *testing.T) { e := NewError(StatusForbidden, "permission denied") - utils.AssertEqual(t, StatusForbidden, e.Code) - utils.AssertEqual(t, "permission denied", e.Message) + require.Equal(t, StatusForbidden, e.Code) + require.Equal(t, "permission denied", e.Message) } // go test -run Test_Test_Timeout func Test_Test_Timeout(t *testing.T) { app := New() - app.config.DisableStartupMessage = true app.Get("/", testEmptyHandler) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), -1) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") app.Get("timeout", func(c Ctx) error { - time.Sleep(55 * time.Millisecond) + time.Sleep(200 * time.Millisecond) return nil }) - _, err = app.Test(httptest.NewRequest(MethodGet, "/timeout", nil), 50) - utils.AssertEqual(t, true, err != nil, "app.Test(req)") + _, err = app.Test(httptest.NewRequest(MethodGet, "/timeout", nil), 20) + require.True(t, err != nil, "app.Test(req)") } type errorReader int @@ -1298,19 +1176,18 @@ func (errorReader) Read([]byte) (int, error) { // go test -run Test_Test_DumpError func Test_Test_DumpError(t *testing.T) { app := New() - app.config.DisableStartupMessage = true app.Get("/", testEmptyHandler) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", errorReader(0))) - utils.AssertEqual(t, true, resp == nil) - utils.AssertEqual(t, "errorReader", err.Error()) + require.True(t, resp == nil) + require.Equal(t, "errorReader", err.Error()) } // go test -run Test_App_Handler func Test_App_Handler(t *testing.T) { h := New().Handler() - utils.AssertEqual(t, "fasthttp.RequestHandler", reflect.TypeOf(h).String()) + require.Equal(t, "fasthttp.RequestHandler", reflect.TypeOf(h).String()) } type invalidView struct{} @@ -1325,7 +1202,7 @@ func Test_App_Init_Error_View(t *testing.T) { defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "implement me", fmt.Sprintf("%v", err)) + require.Equal(t, "implement me", fmt.Sprintf("%v", err)) } }() _ = app.config.Views.Render(nil, "", nil) @@ -1341,16 +1218,16 @@ func Test_App_Stack(t *testing.T) { app.Post("/path3", testEmptyHandler) stack := app.Stack() - utils.AssertEqual(t, 9, len(stack)) - utils.AssertEqual(t, 3, len(stack[methodInt(MethodGet)])) - utils.AssertEqual(t, 3, len(stack[methodInt(MethodHead)])) - utils.AssertEqual(t, 2, len(stack[methodInt(MethodPost)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodPut)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodPatch)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodDelete)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodConnect)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodOptions)])) - utils.AssertEqual(t, 1, len(stack[methodInt(MethodTrace)])) + require.Equal(t, 9, len(stack)) + require.Equal(t, 3, len(stack[methodInt(MethodGet)])) + require.Equal(t, 1, len(stack[methodInt(MethodHead)])) + require.Equal(t, 2, len(stack[methodInt(MethodPost)])) + require.Equal(t, 1, len(stack[methodInt(MethodPut)])) + require.Equal(t, 1, len(stack[methodInt(MethodPatch)])) + require.Equal(t, 1, len(stack[methodInt(MethodDelete)])) + require.Equal(t, 1, len(stack[methodInt(MethodConnect)])) + require.Equal(t, 1, len(stack[methodInt(MethodOptions)])) + require.Equal(t, 1, len(stack[methodInt(MethodTrace)])) } // go test -run Test_App_HandlersCount @@ -1362,16 +1239,15 @@ func Test_App_HandlersCount(t *testing.T) { app.Post("/path3", testEmptyHandler) count := app.HandlersCount() - utils.AssertEqual(t, uint32(4), count) + require.Equal(t, uint32(3), count) } // go test -run Test_App_ReadTimeout func Test_App_ReadTimeout(t *testing.T) { app := New(Config{ - ReadTimeout: time.Nanosecond, - IdleTimeout: time.Minute, - DisableStartupMessage: true, - DisableKeepalive: true, + ReadTimeout: time.Nanosecond, + IdleTimeout: time.Minute, + DisableKeepalive: true, }) app.Get("/read-timeout", func(c Ctx) error { @@ -1382,30 +1258,31 @@ func Test_App_ReadTimeout(t *testing.T) { time.Sleep(500 * time.Millisecond) conn, err := net.Dial(NetworkTCP4, "127.0.0.1:4004") - utils.AssertEqual(t, nil, err) - defer conn.Close() + require.NoError(t, err) + defer func(conn net.Conn) { + err := conn.Close() + require.NoError(t, err) + }(conn) _, err = conn.Write([]byte("HEAD /read-timeout HTTP/1.1\r\n")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) buf := make([]byte, 1024) var n int n, err = conn.Read(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, bytes.Contains(buf[:n], []byte("408 Request Timeout"))) + require.NoError(t, err) + require.True(t, bytes.Contains(buf[:n], []byte("408 Request Timeout"))) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.Listen(":4004")) + require.Nil(t, app.Listen(":4004", ListenConfig{DisableStartupMessage: true})) } // go test -run Test_App_BadRequest func Test_App_BadRequest(t *testing.T) { - app := New(Config{ - DisableStartupMessage: true, - }) + app := New() app.Get("/bad-request", func(c Ctx) error { return c.SendString("I should not be sent") @@ -1414,30 +1291,32 @@ func Test_App_BadRequest(t *testing.T) { go func() { time.Sleep(500 * time.Millisecond) conn, err := net.Dial(NetworkTCP4, "127.0.0.1:4005") - utils.AssertEqual(t, nil, err) - defer conn.Close() + require.NoError(t, err) + defer func(conn net.Conn) { + err := conn.Close() + require.NoError(t, err) + }(conn) _, err = conn.Write([]byte("BadRequest\r\n")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) buf := make([]byte, 1024) var n int n, err = conn.Read(buf) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) - utils.AssertEqual(t, true, bytes.Contains(buf[:n], []byte("400 Bad Request"))) + require.True(t, bytes.Contains(buf[:n], []byte("400 Bad Request"))) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.Listen(":4005")) + require.Nil(t, app.Listen(":4005", ListenConfig{DisableStartupMessage: true})) } // go test -run Test_App_SmallReadBuffer func Test_App_SmallReadBuffer(t *testing.T) { app := New(Config{ - ReadBufferSize: 1, - DisableStartupMessage: true, + ReadBufferSize: 1, }) app.Get("/small-read-buffer", func(c Ctx) error { @@ -1448,72 +1327,19 @@ func Test_App_SmallReadBuffer(t *testing.T) { time.Sleep(500 * time.Millisecond) resp, err := http.Get("http://127.0.0.1:4006/small-read-buffer") if resp != nil { - utils.AssertEqual(t, 431, resp.StatusCode) + require.Equal(t, 431, resp.StatusCode) } - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, nil, app.Shutdown()) + require.NoError(t, err) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.Listen(":4006")) -} - -func captureOutput(f func()) string { - reader, writer, err := os.Pipe() - if err != nil { - panic(err) - } - stdout := os.Stdout - stderr := os.Stderr - defer func() { - os.Stdout = stdout - os.Stderr = stderr - log.SetOutput(os.Stderr) - }() - os.Stdout = writer - os.Stderr = writer - log.SetOutput(writer) - out := make(chan string) - wg := new(sync.WaitGroup) - wg.Add(1) - go func() { - var buf bytes.Buffer - wg.Done() - io.Copy(&buf, reader) - out <- buf.String() - }() - wg.Wait() - f() - writer.Close() - return <-out -} - -func Test_App_Master_Process_Show_Startup_Message(t *testing.T) { - startupMessage := captureOutput(func() { - New(Config{Prefork: true}). - startupMessage(":3000", true, strings.Repeat(",11111,22222,33333,44444,55555,60000", 10)) - }) - fmt.Println(startupMessage) - utils.AssertEqual(t, true, strings.Contains(startupMessage, "https://127.0.0.1:3000")) - utils.AssertEqual(t, true, strings.Contains(startupMessage, "(bound on host 0.0.0.0 and port 3000)")) - utils.AssertEqual(t, true, strings.Contains(startupMessage, "Child PIDs")) - utils.AssertEqual(t, true, strings.Contains(startupMessage, "11111, 22222, 33333, 44444, 55555, 60000")) - utils.AssertEqual(t, true, strings.Contains(startupMessage, "Prefork ........ Enabled")) -} - -func Test_App_Master_Process_Show_Startup_MessageWithAppName(t *testing.T) { - app := New(Config{Prefork: true, AppName: "Test App v1.0.1"}) - startupMessage := captureOutput(func() { - app.startupMessage(":3000", true, strings.Repeat(",11111,22222,33333,44444,55555,60000", 10)) - }) - fmt.Println(startupMessage) - utils.AssertEqual(t, "Test App v1.0.1", app.Config().AppName) - utils.AssertEqual(t, true, strings.Contains(startupMessage, app.Config().AppName)) + require.Nil(t, app.Listen(":4006", ListenConfig{DisableStartupMessage: true})) } func Test_App_Server(t *testing.T) { app := New() - utils.AssertEqual(t, false, app.Server() == nil) + require.False(t, app.Server() == nil) } func Test_App_Error_In_Fasthttp_Server(t *testing.T) { @@ -1524,8 +1350,8 @@ func Test_App_Error_In_Fasthttp_Server(t *testing.T) { app.server.GetOnly = true resp, err := app.Test(httptest.NewRequest(MethodPost, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 500, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 500, resp.StatusCode) } // go test -race -run Test_App_New_Test_Parallel @@ -1533,12 +1359,14 @@ func Test_App_New_Test_Parallel(t *testing.T) { t.Run("Test_App_New_Test_Parallel_1", func(t *testing.T) { t.Parallel() app := New(Config{Immutable: true}) - app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + require.Equal(t, nil, err) }) t.Run("Test_App_New_Test_Parallel_2", func(t *testing.T) { t.Parallel() app := New(Config{Immutable: true}) - app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + require.Equal(t, nil, err) }) } @@ -1550,10 +1378,10 @@ func Test_App_ReadBodyStream(t *testing.T) { }) testString := "this is a test" resp, err := app.Test(httptest.NewRequest("POST", "/", bytes.NewBufferString(testString))) - utils.AssertEqual(t, nil, err, "app.Test(req)") + require.NoError(t, err, "app.Test(req)") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "io.ReadAll(resp.Body)") - utils.AssertEqual(t, fmt.Sprintf("true %s", testString), string(body)) + require.NoError(t, err, "io.ReadAll(resp.Body)") + require.Equal(t, fmt.Sprintf("true %s", testString), string(body)) } func Test_App_DisablePreParseMultipartForm(t *testing.T) { @@ -1587,145 +1415,94 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { b := &bytes.Buffer{} w := multipart.NewWriter(b) writer, err := w.CreateFormFile("test", "test") - utils.AssertEqual(t, nil, err, "w.CreateFormFile") + require.NoError(t, err, "w.CreateFormFile") n, err := writer.Write([]byte(testString)) - utils.AssertEqual(t, nil, err, "writer.Write") - utils.AssertEqual(t, len(testString), n, "writer n") - utils.AssertEqual(t, nil, w.Close(), "w.Close()") + require.NoError(t, err, "writer.Write") + require.Equal(t, len(testString), n, "writer n") + require.Nil(t, w.Close(), "w.Close()") req := httptest.NewRequest("POST", "/", b) req.Header.Set("Content-Type", w.FormDataContentType()) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") + require.NoError(t, err, "app.Test(req)") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "io.ReadAll(resp.Body)") + require.NoError(t, err, "io.ReadAll(resp.Body)") - utils.AssertEqual(t, testString, string(body)) + require.Equal(t, testString, string(body)) } -func Test_App_UseMountedErrorHandler(t *testing.T) { - app := New() +func Test_App_Test_no_timeout_infinitely(t *testing.T) { + var err error + c := make(chan int) - fiber := New(Config{ - ErrorHandler: func(c Ctx, err error) error { - return c.Status(500).SendString("hi, i'm a custom error") - }, - }) - fiber.Get("/", func(c Ctx) error { - return errors.New("something happened") - }) + go func() { + defer func() { c <- 0 }() + app := New() + app.Get("/", func(c Ctx) error { + runtime.Goexit() + return nil + }) + + req := httptest.NewRequest(http.MethodGet, "/", http.NoBody) + _, err = app.Test(req, -1) + }() - app.Use("/api", fiber) + tk := time.NewTimer(5 * time.Second) + defer tk.Stop() - resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil)) - testErrorResponse(t, err, resp, "hi, i'm a custom error") + select { + case <-tk.C: + t.Error("hanging test") + t.FailNow() + case <-c: + } + + if err == nil { + t.Error("unexpected success request") + t.FailNow() + } } -func Test_App_UseMountedErrorHandlerRootLevel(t *testing.T) { - app := New() +func Test_App_SetTLSHandler(t *testing.T) { + tlsHandler := &TLSHandler{clientHelloInfo: &tls.ClientHelloInfo{ + ServerName: "example.golang", + }} - fiber := New(Config{ - ErrorHandler: func(c Ctx, err error) error { - return c.Status(500).SendString("hi, i'm a custom error") - }, - }) - fiber.Get("/api", func(c Ctx) error { - return errors.New("something happened") - }) + app := New() + app.SetTLSHandler(tlsHandler) - app.Use("/", fiber) + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) - resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil)) - testErrorResponse(t, err, resp, "hi, i'm a custom error") + require.Equal(t, "example.golang", c.ClientHelloInfo().ServerName) } -func Test_App_UseMountedErrorHandlerForBestPrefixMatch(t *testing.T) { +func TestApp_GetRoutes(t *testing.T) { app := New() - - tsf := func(c Ctx, err error) error { - return c.Status(200).SendString("hi, i'm a custom sub sub fiber error") - } - tripleSubFiber := New(Config{ - ErrorHandler: tsf, - }) - tripleSubFiber.Get("/", func(c Ctx) error { - return errors.New("something happened") + app.Use(func(c Ctx) error { + return c.Next() }) - - sf := func(c Ctx, err error) error { - return c.Status(200).SendString("hi, i'm a custom sub fiber error") + handler := func(c Ctx) error { + return c.SendStatus(StatusOK) } - subfiber := New(Config{ - ErrorHandler: sf, - }) - subfiber.Get("/", func(c Ctx) error { - return errors.New("something happened") - }) - subfiber.Use("/third", tripleSubFiber) - - f := func(c Ctx, err error) error { - return c.Status(200).SendString("hi, i'm a custom error") + app.Delete("/delete", handler).Name("delete") + app.Post("/post", handler).Name("post") + routes := app.GetRoutes(false) + require.Equal(t, 11, len(routes)) + methodMap := map[string]string{"/delete": "delete", "/post": "post"} + for _, route := range routes { + name, ok := methodMap[route.Path] + if ok { + require.Equal(t, name, route.Name) + } } - fiber := New(Config{ - ErrorHandler: f, - }) - fiber.Get("/", func(c Ctx) error { - return errors.New("something happened") - }) - fiber.Use("/sub", subfiber) - - app.Use("/api", fiber) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub", nil)) - utils.AssertEqual(t, nil, err, "/api/sub req") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - - b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "iotuil.ReadAll()") - utils.AssertEqual(t, "hi, i'm a custom sub fiber error", string(b), "Response body") - - resp2, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub/third", nil)) - utils.AssertEqual(t, nil, err, "/api/sub/third req") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - b, err = io.ReadAll(resp2.Body) - utils.AssertEqual(t, nil, err, "iotuil.ReadAll()") - utils.AssertEqual(t, "hi, i'm a custom sub sub fiber error", string(b), "Third fiber Response body") -} + routes = app.GetRoutes(true) + require.Equal(t, 2, len(routes)) + for _, route := range routes { + name, ok := methodMap[route.Path] + require.Equal(t, true, ok) + require.Equal(t, name, route.Name) + } -func emptyHandler(c Ctx) error { - return nil -} -func Test_App_print_Route(t *testing.T) { - app := New(Config{EnablePrintRoutes: true}) - app.Get("/", emptyHandler).Name("routeName") - printRoutesMessage := captureOutput(func() { - app.printRoutesMessage() - }) - fmt.Println(printRoutesMessage) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "routeName")) -} - -func Test_App_print_Route_with_group(t *testing.T) { - app := New(Config{EnablePrintRoutes: true}) - app.Get("/", emptyHandler) - v1 := app.Group("v1") - v1.Get("/test", emptyHandler).Name("v1") - v1.Post("/test/fiber", emptyHandler) - v1.Put("/test/fiber/*", emptyHandler) - printRoutesMessage := captureOutput(func() { - app.printRoutesMessage() - }) - fmt.Println(printRoutesMessage) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "POST")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "PUT")) - utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber/*")) } diff --git a/bind.go b/bind.go index b390db2fda..147afa0b3a 100644 --- a/bind.go +++ b/bind.go @@ -2,7 +2,7 @@ package fiber import ( "github.com/gofiber/fiber/v3/binder" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // An interface to register custom binders. diff --git a/bind_test.go b/bind_test.go index 6d64f8e059..51101859f4 100644 --- a/bind_test.go +++ b/bind_test.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gofiber/fiber/v3/binder" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -31,23 +31,23 @@ func Test_Bind_Query(t *testing.T) { c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := new(Query) - utils.AssertEqual(t, nil, c.Bind().Query(q)) - utils.AssertEqual(t, 2, len(q.Hobby)) + require.Nil(t, c.Bind().Query(q)) + require.Equal(t, 2, len(q.Hobby)) c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = new(Query) - utils.AssertEqual(t, nil, c.Bind().Query(q)) - utils.AssertEqual(t, 2, len(q.Hobby)) + require.Nil(t, c.Bind().Query(q)) + require.Equal(t, 2, len(q.Hobby)) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = new(Query) - utils.AssertEqual(t, nil, c.Bind().Query(q)) - utils.AssertEqual(t, 3, len(q.Hobby)) + require.Nil(t, c.Bind().Query(q)) + require.Equal(t, 3, len(q.Hobby)) empty := new(Query) c.Request().URI().SetQueryString("") - utils.AssertEqual(t, nil, c.Bind().Query(empty)) - utils.AssertEqual(t, 0, len(empty.Hobby)) + require.Nil(t, c.Bind().Query(empty)) + require.Equal(t, 0, len(empty.Hobby)) type Query2 struct { Bool bool @@ -64,30 +64,30 @@ func Test_Bind_Query(t *testing.T) { q2 := new(Query2) q2.Bool = true q2.Name = "hello world" - utils.AssertEqual(t, nil, c.Bind().Query(q2)) - utils.AssertEqual(t, "basketball,football", q2.Hobby) - utils.AssertEqual(t, true, q2.Bool) - utils.AssertEqual(t, "tom", q2.Name) // check value get overwritten - utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, q2.FavouriteDrinks) + require.Nil(t, c.Bind().Query(q2)) + require.Equal(t, "basketball,football", q2.Hobby) + require.True(t, q2.Bool) + require.Equal(t, "tom", q2.Name) // check value get overwritten + require.Equal(t, []string{"milo", "coke", "pepsi"}, q2.FavouriteDrinks) var nilSlice []string - utils.AssertEqual(t, nilSlice, q2.Empty) - utils.AssertEqual(t, []string{""}, q2.Alloc) - utils.AssertEqual(t, []int64{1}, q2.No) + require.Equal(t, nilSlice, q2.Empty) + require.Equal(t, []string{""}, q2.Alloc) + require.Equal(t, []int64{1}, q2.No) type RequiredQuery struct { Name string `query:"name,required"` } rq := new(RequiredQuery) c.Request().URI().SetQueryString("") - utils.AssertEqual(t, "name is empty", c.Bind().Query(rq).Error()) + require.Equal(t, "name is empty", c.Bind().Query(rq).Error()) type ArrayQuery struct { Data []string } aq := new(ArrayQuery) c.Request().URI().SetQueryString("data[]=john&data[]=doe") - utils.AssertEqual(t, nil, c.Bind().Query(aq)) - utils.AssertEqual(t, 2, len(aq.Data)) + require.Nil(t, c.Bind().Query(aq)) + require.Equal(t, 2, len(aq.Data)) } // go test -run Test_Bind_Query_Map -v @@ -101,32 +101,32 @@ func Test_Bind_Query_Map(t *testing.T) { c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := make(map[string][]string) - utils.AssertEqual(t, nil, c.Bind().Query(&q)) - utils.AssertEqual(t, 2, len(q["hobby"])) + require.Nil(t, c.Bind().Query(&q)) + require.Equal(t, 2, len(q["hobby"])) c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = make(map[string][]string) - utils.AssertEqual(t, nil, c.Bind().Query(&q)) - utils.AssertEqual(t, 2, len(q["hobby"])) + require.Nil(t, c.Bind().Query(&q)) + require.Equal(t, 2, len(q["hobby"])) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = make(map[string][]string) - utils.AssertEqual(t, nil, c.Bind().Query(&q)) - utils.AssertEqual(t, 3, len(q["hobby"])) + require.Nil(t, c.Bind().Query(&q)) + require.Equal(t, 3, len(q["hobby"])) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer") qq := make(map[string]string) - utils.AssertEqual(t, nil, c.Bind().Query(&qq)) - utils.AssertEqual(t, "1", qq["id"]) + require.Nil(t, c.Bind().Query(&qq)) + require.Equal(t, "1", qq["id"]) empty := make(map[string][]string) c.Request().URI().SetQueryString("") - utils.AssertEqual(t, nil, c.Bind().Query(&empty)) - utils.AssertEqual(t, 0, len(empty["hobby"])) + require.Nil(t, c.Bind().Query(&empty)) + require.Equal(t, 0, len(empty["hobby"])) em := make(map[string][]int) c.Request().URI().SetQueryString("") - utils.AssertEqual(t, binder.ErrMapNotConvertable, c.Bind().Query(&em)) + require.Equal(t, binder.ErrMapNotConvertable, c.Bind().Query(&em)) } // go test -run Test_Bind_Query_WithSetParserDecoder -v @@ -166,20 +166,20 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) { q := new(NonRFCTimeInput) c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") - utils.AssertEqual(t, nil, c.Bind().Query(q)) + require.Nil(t, c.Bind().Query(q)) fmt.Println(q.Date, "q.Date") - utils.AssertEqual(t, "CustomDateTest", q.Title) + require.Equal(t, "CustomDateTest", q.Title) date := fmt.Sprintf("%v", q.Date) - utils.AssertEqual(t, "{0 63753609600 }", date) - utils.AssertEqual(t, "October", q.Body) + require.Equal(t, "{0 63753609600 }", date) + require.Equal(t, "October", q.Body) c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October") q = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } - utils.AssertEqual(t, nil, c.Bind().Query(q)) - utils.AssertEqual(t, "", q.Title) + require.Nil(t, c.Bind().Query(q)) + require.Equal(t, "", q.Title) } // go test -run Test_Bind_Query_Schema -v @@ -198,19 +198,19 @@ func Test_Bind_Query_Schema(t *testing.T) { c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("name=tom&nested.age=10") q := new(Query1) - utils.AssertEqual(t, nil, c.Bind().Query(q)) + require.Nil(t, c.Bind().Query(q)) c.Request().URI().SetQueryString("namex=tom&nested.age=10") q = new(Query1) - utils.AssertEqual(t, "name is empty", c.Bind().Query(q).Error()) + require.Equal(t, "name is empty", c.Bind().Query(q).Error()) c.Request().URI().SetQueryString("name=tom&nested.agex=10") q = new(Query1) - utils.AssertEqual(t, nil, c.Bind().Query(q)) + require.Nil(t, c.Bind().Query(q)) c.Request().URI().SetQueryString("name=tom&test.age=10") q = new(Query1) - utils.AssertEqual(t, "nested is empty", c.Bind().Query(q).Error()) + require.Equal(t, "nested is empty", c.Bind().Query(q).Error()) type Query2 struct { Name string `query:"name"` @@ -220,19 +220,19 @@ func Test_Bind_Query_Schema(t *testing.T) { } c.Request().URI().SetQueryString("name=tom&nested.age=10") q2 := new(Query2) - utils.AssertEqual(t, nil, c.Bind().Query(q2)) + require.Nil(t, c.Bind().Query(q2)) c.Request().URI().SetQueryString("nested.age=10") q2 = new(Query2) - utils.AssertEqual(t, nil, c.Bind().Query(q2)) + require.Nil(t, c.Bind().Query(q2)) c.Request().URI().SetQueryString("nested.agex=10") q2 = new(Query2) - utils.AssertEqual(t, "nested.age is empty", c.Bind().Query(q2).Error()) + require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error()) c.Request().URI().SetQueryString("nested.agex=10") q2 = new(Query2) - utils.AssertEqual(t, "nested.age is empty", c.Bind().Query(q2).Error()) + require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error()) type Node struct { Value int `query:"val,required"` @@ -240,20 +240,20 @@ func Test_Bind_Query_Schema(t *testing.T) { } c.Request().URI().SetQueryString("val=1&next.val=3") n := new(Node) - utils.AssertEqual(t, nil, c.Bind().Query(n)) - utils.AssertEqual(t, 1, n.Value) - utils.AssertEqual(t, 3, n.Next.Value) + require.Nil(t, c.Bind().Query(n)) + require.Equal(t, 1, n.Value) + require.Equal(t, 3, n.Next.Value) c.Request().URI().SetQueryString("next.val=2") n = new(Node) - utils.AssertEqual(t, "val is empty", c.Bind().Query(n).Error()) + require.Equal(t, "val is empty", c.Bind().Query(n).Error()) c.Request().URI().SetQueryString("val=3&next.value=2") n = new(Node) n.Next = new(Node) - utils.AssertEqual(t, nil, c.Bind().Query(n)) - utils.AssertEqual(t, 3, n.Value) - utils.AssertEqual(t, 0, n.Next.Value) + require.Nil(t, c.Bind().Query(n)) + require.Equal(t, 3, n.Value) + require.Equal(t, 0, n.Next.Value) type Person struct { Name string `query:"name"` @@ -266,21 +266,21 @@ func Test_Bind_Query_Schema(t *testing.T) { 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.Bind().Query(cq)) - utils.AssertEqual(t, 2, len(cq.Data)) - utils.AssertEqual(t, "john", cq.Data[0].Name) - utils.AssertEqual(t, 10, cq.Data[0].Age) - utils.AssertEqual(t, "doe", cq.Data[1].Name) - utils.AssertEqual(t, 12, cq.Data[1].Age) + require.Nil(t, c.Bind().Query(cq)) + require.Equal(t, 2, len(cq.Data)) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, 10, cq.Data[0].Age) + require.Equal(t, "doe", cq.Data[1].Name) + require.Equal(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, nil, c.Bind().Query(cq)) - utils.AssertEqual(t, 2, len(cq.Data)) - utils.AssertEqual(t, "john", cq.Data[0].Name) - utils.AssertEqual(t, 10, cq.Data[0].Age) - utils.AssertEqual(t, "doe", cq.Data[1].Name) - utils.AssertEqual(t, 12, cq.Data[1].Age) + require.Nil(t, c.Bind().Query(cq)) + require.Equal(t, 2, len(cq.Data)) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, 10, cq.Data[0].Age) + require.Equal(t, "doe", cq.Data[1].Name) + require.Equal(t, 12, cq.Data[1].Age) } // go test -run Test_Bind_Header -v @@ -301,19 +301,19 @@ func Test_Bind_Header(t *testing.T) { c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := new(Header) - utils.AssertEqual(t, nil, c.Bind().Header(q)) - utils.AssertEqual(t, 2, len(q.Hobby)) + require.Nil(t, c.Bind().Header(q)) + require.Equal(t, 2, len(q.Hobby)) c.Request().Header.Del("hobby") c.Request().Header.Add("Hobby", "golang,fiber,go") q = new(Header) - utils.AssertEqual(t, nil, c.Bind().Header(q)) - utils.AssertEqual(t, 3, len(q.Hobby)) + require.Nil(t, c.Bind().Header(q)) + require.Equal(t, 3, len(q.Hobby)) empty := new(Header) c.Request().Header.Del("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(empty)) - utils.AssertEqual(t, 0, len(empty.Hobby)) + require.Nil(t, c.Bind().Query(empty)) + require.Equal(t, 0, len(empty.Hobby)) type Header2 struct { Bool bool @@ -337,22 +337,22 @@ func Test_Bind_Header(t *testing.T) { h2 := new(Header2) h2.Bool = true h2.Name = "hello world" - utils.AssertEqual(t, nil, c.Bind().Header(h2)) - utils.AssertEqual(t, "go,fiber", h2.Hobby) - utils.AssertEqual(t, true, h2.Bool) - utils.AssertEqual(t, "Jane Doe", h2.Name) // check value get overwritten - utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) + require.Nil(t, c.Bind().Header(h2)) + require.Equal(t, "go,fiber", h2.Hobby) + require.True(t, h2.Bool) + require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten + require.Equal(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) var nilSlice []string - utils.AssertEqual(t, nilSlice, h2.Empty) - utils.AssertEqual(t, []string{""}, h2.Alloc) - utils.AssertEqual(t, []int64{1}, h2.No) + require.Equal(t, nilSlice, h2.Empty) + require.Equal(t, []string{""}, h2.Alloc) + require.Equal(t, []int64{1}, h2.No) type RequiredHeader struct { Name string `header:"name,required"` } rh := new(RequiredHeader) c.Request().Header.Del("name") - utils.AssertEqual(t, "name is empty", c.Bind().Header(rh).Error()) + require.Equal(t, "name is empty", c.Bind().Header(rh).Error()) } // go test -run Test_Bind_Header_Map -v @@ -369,19 +369,19 @@ func Test_Bind_Header_Map(t *testing.T) { c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) - utils.AssertEqual(t, nil, c.Bind().Header(&q)) - utils.AssertEqual(t, 2, len(q["Hobby"])) + require.Nil(t, c.Bind().Header(&q)) + require.Equal(t, 2, len(q["Hobby"])) c.Request().Header.Del("hobby") c.Request().Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) - utils.AssertEqual(t, nil, c.Bind().Header(&q)) - utils.AssertEqual(t, 3, len(q["Hobby"])) + require.Nil(t, c.Bind().Header(&q)) + require.Equal(t, 3, len(q["Hobby"])) empty := make(map[string][]string, 0) c.Request().Header.Del("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(&empty)) - utils.AssertEqual(t, 0, len(empty["Hobby"])) + require.Nil(t, c.Bind().Query(&empty)) + require.Equal(t, 0, len(empty["Hobby"])) } // go test -run Test_Bind_Header_WithSetParserDecoder -v @@ -424,20 +424,20 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) { c.Request().Header.Add("Title", "CustomDateTest") c.Request().Header.Add("Body", "October") - utils.AssertEqual(t, nil, c.Bind().Header(r)) + require.Nil(t, c.Bind().Header(r)) fmt.Println(r.Date, "q.Date") - utils.AssertEqual(t, "CustomDateTest", r.Title) + require.Equal(t, "CustomDateTest", r.Title) date := fmt.Sprintf("%v", r.Date) - utils.AssertEqual(t, "{0 63753609600 }", date) - utils.AssertEqual(t, "October", r.Body) + require.Equal(t, "{0 63753609600 }", date) + require.Equal(t, "October", r.Body) c.Request().Header.Add("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } - utils.AssertEqual(t, nil, c.Bind().Header(r)) - utils.AssertEqual(t, "", r.Title) + require.Nil(t, c.Bind().Header(r)) + require.Equal(t, "", r.Title) } // go test -run Test_Bind_Header_Schema -v @@ -458,21 +458,21 @@ func Test_Bind_Header_Schema(t *testing.T) { c.Request().Header.Add("Name", "tom") c.Request().Header.Add("Nested.Age", "10") q := new(Header1) - utils.AssertEqual(t, nil, c.Bind().Header(q)) + require.Nil(t, c.Bind().Header(q)) c.Request().Header.Del("Name") q = new(Header1) - utils.AssertEqual(t, "Name is empty", c.Bind().Header(q).Error()) + require.Equal(t, "Name is empty", c.Bind().Header(q).Error()) c.Request().Header.Add("Name", "tom") c.Request().Header.Del("Nested.Age") c.Request().Header.Add("Nested.Agex", "10") q = new(Header1) - utils.AssertEqual(t, nil, c.Bind().Header(q)) + require.Nil(t, c.Bind().Header(q)) c.Request().Header.Del("Nested.Agex") q = new(Header1) - utils.AssertEqual(t, "Nested is empty", c.Bind().Header(q).Error()) + require.Equal(t, "Nested is empty", c.Bind().Header(q).Error()) c.Request().Header.Del("Nested.Agex") c.Request().Header.Del("Name") @@ -488,17 +488,17 @@ func Test_Bind_Header_Schema(t *testing.T) { c.Request().Header.Add("Nested.Age", "10") h2 := new(Header2) - utils.AssertEqual(t, nil, c.Bind().Header(h2)) + require.Nil(t, c.Bind().Header(h2)) c.Request().Header.Del("Name") h2 = new(Header2) - utils.AssertEqual(t, nil, c.Bind().Header(h2)) + require.Nil(t, c.Bind().Header(h2)) c.Request().Header.Del("Name") c.Request().Header.Del("Nested.Age") c.Request().Header.Add("Nested.Agex", "10") h2 = new(Header2) - utils.AssertEqual(t, "Nested.age is empty", c.Bind().Header(h2).Error()) + require.Equal(t, "Nested.age is empty", c.Bind().Header(h2).Error()) type Node struct { Value int `header:"Val,required"` @@ -507,22 +507,22 @@ func Test_Bind_Header_Schema(t *testing.T) { c.Request().Header.Add("Val", "1") c.Request().Header.Add("Next.Val", "3") n := new(Node) - utils.AssertEqual(t, nil, c.Bind().Header(n)) - utils.AssertEqual(t, 1, n.Value) - utils.AssertEqual(t, 3, n.Next.Value) + require.Nil(t, c.Bind().Header(n)) + require.Equal(t, 1, n.Value) + require.Equal(t, 3, n.Next.Value) c.Request().Header.Del("Val") n = new(Node) - utils.AssertEqual(t, "Val is empty", c.Bind().Header(n).Error()) + require.Equal(t, "Val is empty", c.Bind().Header(n).Error()) c.Request().Header.Add("Val", "3") c.Request().Header.Del("Next.Val") c.Request().Header.Add("Next.Value", "2") n = new(Node) n.Next = new(Node) - utils.AssertEqual(t, nil, c.Bind().Header(n)) - utils.AssertEqual(t, 3, n.Value) - utils.AssertEqual(t, 0, n.Next.Value) + require.Nil(t, c.Bind().Header(n)) + require.Equal(t, 3, n.Value) + require.Equal(t, 0, n.Next.Value) } // go test -run Test_Bind_Resp_Header -v @@ -543,19 +543,19 @@ func Test_Bind_RespHeader(t *testing.T) { c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := new(Header) - utils.AssertEqual(t, nil, c.Bind().RespHeader(q)) - utils.AssertEqual(t, 2, len(q.Hobby)) + require.Nil(t, c.Bind().RespHeader(q)) + require.Equal(t, 2, len(q.Hobby)) c.Response().Header.Del("hobby") c.Response().Header.Add("Hobby", "golang,fiber,go") q = new(Header) - utils.AssertEqual(t, nil, c.Bind().RespHeader(q)) - utils.AssertEqual(t, 3, len(q.Hobby)) + require.Nil(t, c.Bind().RespHeader(q)) + require.Equal(t, 3, len(q.Hobby)) empty := new(Header) c.Response().Header.Del("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(empty)) - utils.AssertEqual(t, 0, len(empty.Hobby)) + require.Nil(t, c.Bind().Query(empty)) + require.Equal(t, 0, len(empty.Hobby)) type Header2 struct { Bool bool @@ -579,22 +579,22 @@ func Test_Bind_RespHeader(t *testing.T) { h2 := new(Header2) h2.Bool = true h2.Name = "hello world" - utils.AssertEqual(t, nil, c.Bind().RespHeader(h2)) - utils.AssertEqual(t, "go,fiber", h2.Hobby) - utils.AssertEqual(t, true, h2.Bool) - utils.AssertEqual(t, "Jane Doe", h2.Name) // check value get overwritten - utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) + require.Nil(t, c.Bind().RespHeader(h2)) + require.Equal(t, "go,fiber", h2.Hobby) + require.True(t, h2.Bool) + require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten + require.Equal(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) var nilSlice []string - utils.AssertEqual(t, nilSlice, h2.Empty) - utils.AssertEqual(t, []string{""}, h2.Alloc) - utils.AssertEqual(t, []int64{1}, h2.No) + require.Equal(t, nilSlice, h2.Empty) + require.Equal(t, []string{""}, h2.Alloc) + require.Equal(t, []int64{1}, h2.No) type RequiredHeader struct { Name string `respHeader:"name,required"` } rh := new(RequiredHeader) c.Response().Header.Del("name") - utils.AssertEqual(t, "name is empty", c.Bind().RespHeader(rh).Error()) + require.Equal(t, "name is empty", c.Bind().RespHeader(rh).Error()) } // go test -run Test_Bind_RespHeader_Map -v @@ -611,19 +611,19 @@ func Test_Bind_RespHeader_Map(t *testing.T) { c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) - utils.AssertEqual(t, nil, c.Bind().RespHeader(&q)) - utils.AssertEqual(t, 2, len(q["Hobby"])) + require.Nil(t, c.Bind().RespHeader(&q)) + require.Equal(t, 2, len(q["Hobby"])) c.Response().Header.Del("hobby") c.Response().Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) - utils.AssertEqual(t, nil, c.Bind().RespHeader(&q)) - utils.AssertEqual(t, 3, len(q["Hobby"])) + require.Nil(t, c.Bind().RespHeader(&q)) + require.Equal(t, 3, len(q["Hobby"])) empty := make(map[string][]string, 0) c.Response().Header.Del("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(&empty)) - utils.AssertEqual(t, 0, len(empty["Hobby"])) + require.Nil(t, c.Bind().Query(&empty)) + require.Equal(t, 0, len(empty["Hobby"])) } // go test -v -run=^$ -bench=Benchmark_Bind_Query -benchmem -count=4 @@ -645,7 +645,7 @@ func Benchmark_Bind_Query(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Query(q) } - utils.AssertEqual(b, nil, c.Bind().Query(q)) + require.Nil(b, c.Bind().Query(q)) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_Map -benchmem -count=4 @@ -662,7 +662,7 @@ func Benchmark_Bind_Query_Map(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Query(&q) } - utils.AssertEqual(b, nil, c.Bind().Query(&q)) + require.Nil(b, c.Bind().Query(&q)) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_WithParseParam -benchmem -count=4 @@ -690,7 +690,7 @@ func Benchmark_Bind_Query_WithParseParam(b *testing.B) { c.Bind().Query(cq) } - utils.AssertEqual(b, nil, c.Bind().Query(cq)) + require.Nil(b, c.Bind().Query(cq)) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_Comma -benchmem -count=4 @@ -713,7 +713,7 @@ func Benchmark_Bind_Query_Comma(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Query(q) } - utils.AssertEqual(b, nil, c.Bind().Query(q)) + require.Nil(b, c.Bind().Query(q)) } // go test -v -run=^$ -bench=Benchmark_Bind_Header -benchmem -count=4 @@ -739,7 +739,7 @@ func Benchmark_Bind_Header(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Header(q) } - utils.AssertEqual(b, nil, c.Bind().Header(q)) + require.Nil(b, c.Bind().Header(q)) } // go test -v -run=^$ -bench=Benchmark_Bind_Header_Map -benchmem -count=4 @@ -760,7 +760,7 @@ func Benchmark_Bind_Header_Map(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Header(&q) } - utils.AssertEqual(b, nil, c.Bind().Header(&q)) + require.Nil(b, c.Bind().Header(&q)) } // go test -v -run=^$ -bench=Benchmark_Bind_RespHeader -benchmem -count=4 @@ -786,7 +786,7 @@ func Benchmark_Bind_RespHeader(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().RespHeader(q) } - utils.AssertEqual(b, nil, c.Bind().RespHeader(q)) + require.Nil(b, c.Bind().RespHeader(q)) } // go test -v -run=^$ -bench=Benchmark_Bind_RespHeader_Map -benchmem -count=4 @@ -807,7 +807,7 @@ func Benchmark_Bind_RespHeader_Map(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().RespHeader(&q) } - utils.AssertEqual(b, nil, c.Bind().RespHeader(&q)) + require.Nil(b, c.Bind().RespHeader(&q)) } // go test -run Test_Bind_Body @@ -831,8 +831,8 @@ func Test_Bind_Body(t *testing.T) { c.Request().SetBody(gzipJSON.Bytes()) c.Request().Header.SetContentLength(len(gzipJSON.Bytes())) d := new(Demo) - utils.AssertEqual(t, nil, c.Bind().Body(d)) - utils.AssertEqual(t, "john", d.Name) + require.Nil(t, c.Bind().Body(d)) + require.Equal(t, "john", d.Name) c.Request().Header.Del(HeaderContentEncoding) } @@ -841,8 +841,8 @@ func Test_Bind_Body(t *testing.T) { c.Request().SetBody([]byte(body)) c.Request().Header.SetContentLength(len(body)) d := new(Demo) - utils.AssertEqual(t, nil, c.Bind().Body(d)) - utils.AssertEqual(t, "john", d.Name) + require.Nil(t, c.Bind().Body(d)) + require.Equal(t, "john", d.Name) } testDecodeParser(MIMEApplicationJSON, `{"name":"john"}`) @@ -854,7 +854,7 @@ func Test_Bind_Body(t *testing.T) { c.Request().Header.SetContentType(contentType) c.Request().SetBody([]byte(body)) c.Request().Header.SetContentLength(len(body)) - utils.AssertEqual(t, false, c.Bind().Body(nil) == nil) + require.False(t, c.Bind().Body(nil) == nil) } testDecodeParserError("invalid-content-type", "") @@ -869,20 +869,20 @@ func Test_Bind_Body(t *testing.T) { c.Request().SetBody([]byte("data[0][name]=john&data[1][name]=doe")) c.Request().Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) - utils.AssertEqual(t, nil, c.Bind().Body(cq)) - utils.AssertEqual(t, 2, len(cq.Data)) - utils.AssertEqual(t, "john", cq.Data[0].Name) - utils.AssertEqual(t, "doe", cq.Data[1].Name) + require.Nil(t, c.Bind().Body(cq)) + require.Equal(t, 2, len(cq.Data)) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, "doe", cq.Data[1].Name) c.Request().Reset() c.Request().Header.SetContentType(MIMEApplicationForm) c.Request().SetBody([]byte("data.0.name=john&data.1.name=doe")) c.Request().Header.SetContentLength(len(c.Body())) cq = new(CollectionQuery) - utils.AssertEqual(t, nil, c.Bind().Body(cq)) - utils.AssertEqual(t, 2, len(cq.Data)) - utils.AssertEqual(t, "john", cq.Data[0].Name) - utils.AssertEqual(t, "doe", cq.Data[1].Name) + require.Nil(t, c.Bind().Body(cq)) + require.Equal(t, 2, len(cq.Data)) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, "doe", cq.Data[1].Name) } // go test -run Test_Bind_Body_WithSetParserDecoder @@ -925,11 +925,11 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) { Title: "Existing title", Body: "Existing Body", } - utils.AssertEqual(t, nil, c.Bind().Body(&d)) + require.Nil(t, c.Bind().Body(&d)) date := fmt.Sprintf("%v", d.Date) - utils.AssertEqual(t, "{0 63743587200 }", date) - utils.AssertEqual(t, "", d.Title) - utils.AssertEqual(t, "New Body", d.Body) + require.Equal(t, "{0 63743587200 }", date) + require.Equal(t, "", d.Title) + require.Equal(t, "New Body", d.Body) } testDecodeParser(MIMEApplicationForm, "date=2020-12-15&title=&body=New Body") @@ -956,8 +956,8 @@ func Benchmark_Bind_Body_JSON(b *testing.B) { for n := 0; n < b.N; n++ { _ = c.Bind().Body(d) } - utils.AssertEqual(b, nil, c.Bind().Body(d)) - utils.AssertEqual(b, "john", d.Name) + require.Nil(b, c.Bind().Body(d)) + require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_XML -benchmem -count=4 @@ -980,8 +980,8 @@ func Benchmark_Bind_Body_XML(b *testing.B) { for n := 0; n < b.N; n++ { _ = c.Bind().Body(d) } - utils.AssertEqual(b, nil, c.Bind().Body(d)) - utils.AssertEqual(b, "john", d.Name) + require.Nil(b, c.Bind().Body(d)) + require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form -benchmem -count=4 @@ -1004,8 +1004,8 @@ func Benchmark_Bind_Body_Form(b *testing.B) { for n := 0; n < b.N; n++ { _ = c.Bind().Body(d) } - utils.AssertEqual(b, nil, c.Bind().Body(d)) - utils.AssertEqual(b, "john", d.Name) + require.Nil(b, c.Bind().Body(d)) + require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm -benchmem -count=4 @@ -1029,8 +1029,8 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { for n := 0; n < b.N; n++ { _ = c.Bind().Body(d) } - utils.AssertEqual(b, nil, c.Bind().Body(d)) - utils.AssertEqual(b, "john", d.Name) + require.Nil(b, c.Bind().Body(d)) + require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form_Map -benchmem -count=4 @@ -1050,8 +1050,8 @@ func Benchmark_Bind_Body_Form_Map(b *testing.B) { for n := 0; n < b.N; n++ { _ = c.Bind().Body(&d) } - utils.AssertEqual(b, nil, c.Bind().Body(&d)) - utils.AssertEqual(b, "john", d["name"]) + require.Nil(b, c.Bind().Body(&d)) + require.Equal(b, "john", d["name"]) } // go test -run Test_Bind_URI @@ -1070,8 +1070,8 @@ func Test_Bind_URI(t *testing.T) { if err := c.Bind().URI(d); err != nil { t.Fatal(err) } - utils.AssertEqual(t, uint(111), d.UserID) - utils.AssertEqual(t, uint(222), d.RoleID) + require.Equal(t, uint(111), d.UserID) + require.Equal(t, uint(222), d.RoleID) return nil }) app.Test(httptest.NewRequest(MethodGet, "/test1/111/role/222", nil)) @@ -1089,8 +1089,8 @@ func Test_Bind_URI_Map(t *testing.T) { if err := c.Bind().URI(&d); err != nil { t.Fatal(err) } - utils.AssertEqual(t, uint(111), d["userId"]) - utils.AssertEqual(t, uint(222), d["roleId"]) + require.Equal(t, uint(111), d["userId"]) + require.Equal(t, uint(222), d["roleId"]) return nil }) app.Test(httptest.NewRequest(MethodGet, "/test1/111/role/222", nil)) @@ -1125,10 +1125,10 @@ func Benchmark_Bind_URI(b *testing.B) { c.Bind().URI(&res) } - utils.AssertEqual(b, "john", res.Param1) - utils.AssertEqual(b, "doe", res.Param2) - utils.AssertEqual(b, "is", res.Param3) - utils.AssertEqual(b, "awesome", res.Param4) + require.Equal(b, "john", res.Param1) + require.Equal(b, "doe", res.Param2) + require.Equal(b, "is", res.Param3) + require.Equal(b, "awesome", res.Param4) } // go test -v -run=^$ -bench=Benchmark_Bind_URI_Map -benchmem -count=4 @@ -1154,10 +1154,10 @@ func Benchmark_Bind_URI_Map(b *testing.B) { c.Bind().URI(&res) } - utils.AssertEqual(b, "john", res["param1"]) - utils.AssertEqual(b, "doe", res["param2"]) - utils.AssertEqual(b, "is", res["param3"]) - utils.AssertEqual(b, "awesome", res["param4"]) + require.Equal(b, "john", res["param1"]) + require.Equal(b, "doe", res["param2"]) + require.Equal(b, "is", res["param3"]) + require.Equal(b, "awesome", res["param4"]) } // go test -run Test_Bind_Cookie -v @@ -1179,19 +1179,19 @@ func Test_Bind_Cookie(t *testing.T) { c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := new(Cookie) - utils.AssertEqual(t, nil, c.Bind().Cookie(q)) - utils.AssertEqual(t, 2, len(q.Hobby)) + require.Nil(t, c.Bind().Cookie(q)) + require.Equal(t, 2, len(q.Hobby)) c.Request().Header.DelCookie("hobby") c.Request().Header.SetCookie("Hobby", "golang,fiber,go") q = new(Cookie) - utils.AssertEqual(t, nil, c.Bind().Cookie(q)) - utils.AssertEqual(t, 3, len(q.Hobby)) + require.Nil(t, c.Bind().Cookie(q)) + require.Equal(t, 3, len(q.Hobby)) empty := new(Cookie) c.Request().Header.DelCookie("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(empty)) - utils.AssertEqual(t, 0, len(empty.Hobby)) + require.Nil(t, c.Bind().Query(empty)) + require.Equal(t, 0, len(empty.Hobby)) type Cookie2 struct { Bool bool @@ -1215,22 +1215,22 @@ func Test_Bind_Cookie(t *testing.T) { h2 := new(Cookie2) h2.Bool = true h2.Name = "hello world" - utils.AssertEqual(t, nil, c.Bind().Cookie(h2)) - utils.AssertEqual(t, "go,fiber", h2.Hobby) - utils.AssertEqual(t, true, h2.Bool) - utils.AssertEqual(t, "Jane Doe", h2.Name) // check value get overwritten - utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) + require.Nil(t, c.Bind().Cookie(h2)) + require.Equal(t, "go,fiber", h2.Hobby) + require.True(t, h2.Bool) + require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten + require.Equal(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) var nilSlice []string - utils.AssertEqual(t, nilSlice, h2.Empty) - utils.AssertEqual(t, []string{""}, h2.Alloc) - utils.AssertEqual(t, []int64{1}, h2.No) + require.Equal(t, nilSlice, h2.Empty) + require.Equal(t, []string{""}, h2.Alloc) + require.Equal(t, []int64{1}, h2.No) type RequiredCookie struct { Name string `cookie:"name,required"` } rh := new(RequiredCookie) c.Request().Header.DelCookie("name") - utils.AssertEqual(t, "name is empty", c.Bind().Cookie(rh).Error()) + require.Equal(t, "name is empty", c.Bind().Cookie(rh).Error()) } // go test -run Test_Bind_Cookie_Map -v @@ -1247,19 +1247,19 @@ func Test_Bind_Cookie_Map(t *testing.T) { c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := make(map[string][]string) - utils.AssertEqual(t, nil, c.Bind().Cookie(&q)) - utils.AssertEqual(t, 2, len(q["Hobby"])) + require.Nil(t, c.Bind().Cookie(&q)) + require.Equal(t, 2, len(q["Hobby"])) c.Request().Header.DelCookie("hobby") c.Request().Header.SetCookie("Hobby", "golang,fiber,go") q = make(map[string][]string) - utils.AssertEqual(t, nil, c.Bind().Cookie(&q)) - utils.AssertEqual(t, 3, len(q["Hobby"])) + require.Nil(t, c.Bind().Cookie(&q)) + require.Equal(t, 3, len(q["Hobby"])) empty := make(map[string][]string) c.Request().Header.DelCookie("hobby") - utils.AssertEqual(t, nil, c.Bind().Query(&empty)) - utils.AssertEqual(t, 0, len(empty["Hobby"])) + require.Nil(t, c.Bind().Query(&empty)) + require.Equal(t, 0, len(empty["Hobby"])) } // go test -run Test_Bind_Cookie_WithSetParserDecoder -v @@ -1302,20 +1302,20 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) { c.Request().Header.SetCookie("Title", "CustomDateTest") c.Request().Header.SetCookie("Body", "October") - utils.AssertEqual(t, nil, c.Bind().Cookie(r)) + require.Nil(t, c.Bind().Cookie(r)) fmt.Println(r.Date, "q.Date") - utils.AssertEqual(t, "CustomDateTest", r.Title) + require.Equal(t, "CustomDateTest", r.Title) date := fmt.Sprintf("%v", r.Date) - utils.AssertEqual(t, "{0 63753609600 }", date) - utils.AssertEqual(t, "October", r.Body) + require.Equal(t, "{0 63753609600 }", date) + require.Equal(t, "October", r.Body) c.Request().Header.SetCookie("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } - utils.AssertEqual(t, nil, c.Bind().Cookie(r)) - utils.AssertEqual(t, "", r.Title) + require.Nil(t, c.Bind().Cookie(r)) + require.Equal(t, "", r.Title) } // go test -run Test_Bind_Cookie_Schema -v @@ -1337,21 +1337,21 @@ func Test_Bind_Cookie_Schema(t *testing.T) { c.Request().Header.SetCookie("Name", "tom") c.Request().Header.SetCookie("Nested.Age", "10") q := new(Cookie1) - utils.AssertEqual(t, nil, c.Bind().Cookie(q)) + require.Nil(t, c.Bind().Cookie(q)) c.Request().Header.DelCookie("Name") q = new(Cookie1) - utils.AssertEqual(t, "Name is empty", c.Bind().Cookie(q).Error()) + require.Equal(t, "Name is empty", c.Bind().Cookie(q).Error()) c.Request().Header.SetCookie("Name", "tom") c.Request().Header.DelCookie("Nested.Age") c.Request().Header.SetCookie("Nested.Agex", "10") q = new(Cookie1) - utils.AssertEqual(t, nil, c.Bind().Cookie(q)) + require.Nil(t, c.Bind().Cookie(q)) c.Request().Header.DelCookie("Nested.Agex") q = new(Cookie1) - utils.AssertEqual(t, "Nested is empty", c.Bind().Cookie(q).Error()) + require.Equal(t, "Nested is empty", c.Bind().Cookie(q).Error()) c.Request().Header.DelCookie("Nested.Agex") c.Request().Header.DelCookie("Name") @@ -1367,17 +1367,17 @@ func Test_Bind_Cookie_Schema(t *testing.T) { c.Request().Header.SetCookie("Nested.Age", "10") h2 := new(Cookie2) - utils.AssertEqual(t, nil, c.Bind().Cookie(h2)) + require.Nil(t, c.Bind().Cookie(h2)) c.Request().Header.DelCookie("Name") h2 = new(Cookie2) - utils.AssertEqual(t, nil, c.Bind().Cookie(h2)) + require.Nil(t, c.Bind().Cookie(h2)) c.Request().Header.DelCookie("Name") c.Request().Header.DelCookie("Nested.Age") c.Request().Header.SetCookie("Nested.Agex", "10") h2 = new(Cookie2) - utils.AssertEqual(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error()) + require.Equal(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error()) type Node struct { Value int `cookie:"Val,required"` @@ -1386,22 +1386,22 @@ func Test_Bind_Cookie_Schema(t *testing.T) { c.Request().Header.SetCookie("Val", "1") c.Request().Header.SetCookie("Next.Val", "3") n := new(Node) - utils.AssertEqual(t, nil, c.Bind().Cookie(n)) - utils.AssertEqual(t, 1, n.Value) - utils.AssertEqual(t, 3, n.Next.Value) + require.Nil(t, c.Bind().Cookie(n)) + require.Equal(t, 1, n.Value) + require.Equal(t, 3, n.Next.Value) c.Request().Header.DelCookie("Val") n = new(Node) - utils.AssertEqual(t, "Val is empty", c.Bind().Cookie(n).Error()) + require.Equal(t, "Val is empty", c.Bind().Cookie(n).Error()) c.Request().Header.SetCookie("Val", "3") c.Request().Header.DelCookie("Next.Val") c.Request().Header.SetCookie("Next.Value", "2") n = new(Node) n.Next = new(Node) - utils.AssertEqual(t, nil, c.Bind().Cookie(n)) - utils.AssertEqual(t, 3, n.Value) - utils.AssertEqual(t, 0, n.Next.Value) + require.Nil(t, c.Bind().Cookie(n)) + require.Equal(t, 3, n.Value) + require.Equal(t, 0, n.Next.Value) } // go test -v -run=^$ -bench=Benchmark_Bind_Cookie -benchmem -count=4 @@ -1428,7 +1428,7 @@ func Benchmark_Bind_Cookie(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Cookie(q) } - utils.AssertEqual(b, nil, c.Bind().Cookie(q)) + require.Nil(b, c.Bind().Cookie(q)) } // go test -v -run=^$ -bench=Benchmark_Bind_Cookie_Map -benchmem -count=4 @@ -1450,7 +1450,7 @@ func Benchmark_Bind_Cookie_Map(b *testing.B) { for n := 0; n < b.N; n++ { c.Bind().Cookie(&q) } - utils.AssertEqual(b, nil, c.Bind().Cookie(&q)) + require.Nil(b, c.Bind().Cookie(&q)) } // custom binder for testing @@ -1486,10 +1486,10 @@ func Test_Bind_CustomBinder(t *testing.T) { c.Request().Header.SetContentLength(len(body)) d := new(Demo) - utils.AssertEqual(t, nil, c.Bind().Body(d)) - utils.AssertEqual(t, nil, c.Bind().Custom("custom", d)) - utils.AssertEqual(t, ErrCustomBinderNotFound, c.Bind().Custom("not_custom", d)) - utils.AssertEqual(t, "john", d.Name) + require.Nil(t, c.Bind().Body(d)) + require.Nil(t, c.Bind().Custom("custom", d)) + require.Equal(t, ErrCustomBinderNotFound, c.Bind().Custom("not_custom", d)) + require.Equal(t, "john", d.Name) } // go test -run Test_Bind_Must @@ -1503,8 +1503,8 @@ func Test_Bind_Must(t *testing.T) { rq := new(RequiredQuery) c.Request().URI().SetQueryString("") err := c.Bind().Must().Query(rq) - utils.AssertEqual(t, StatusBadRequest, c.Response().StatusCode()) - utils.AssertEqual(t, "Bad request: name is empty", err.Error()) + require.Equal(t, StatusBadRequest, c.Response().StatusCode()) + require.Equal(t, "Bad request: name is empty", err.Error()) } // simple struct validator for testing @@ -1536,9 +1536,58 @@ func Test_Bind_StructValidator(t *testing.T) { rq := new(simpleQuery) c.Request().URI().SetQueryString("name=efe") - utils.AssertEqual(t, "you should have entered right name!", c.Bind().Query(rq).Error()) + require.Equal(t, "you should have entered right name!", c.Bind().Query(rq).Error()) rq = new(simpleQuery) c.Request().URI().SetQueryString("name=john") - utils.AssertEqual(t, nil, c.Bind().Query(rq)) + require.Nil(t, c.Bind().Query(rq)) +} + +// go test -run Test_Bind_RepeatParserWithSameStruct -v +func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + + type Request struct { + QueryParam string `query:"query_param"` + HeaderParam string `header:"header_param"` + BodyParam string `json:"body_param" xml:"body_param" form:"body_param"` + } + + r := new(Request) + + c.Request().URI().SetQueryString("query_param=query_param") + require.Equal(t, nil, c.Bind().Query(r)) + require.Equal(t, "query_param", r.QueryParam) + + c.Request().Header.Add("header_param", "header_param") + require.Equal(t, nil, c.Bind().Header(r)) + require.Equal(t, "header_param", r.HeaderParam) + + var gzipJSON bytes.Buffer + w := gzip.NewWriter(&gzipJSON) + _, _ = w.Write([]byte(`{"body_param":"body_param"}`)) + _ = w.Close() + c.Request().Header.SetContentType(MIMEApplicationJSON) + c.Request().Header.Set(HeaderContentEncoding, "gzip") + c.Request().SetBody(gzipJSON.Bytes()) + c.Request().Header.SetContentLength(len(gzipJSON.Bytes())) + require.Equal(t, nil, c.Bind().Body(r)) + require.Equal(t, "body_param", r.BodyParam) + c.Request().Header.Del(HeaderContentEncoding) + + testDecodeParser := func(contentType, body string) { + c.Request().Header.SetContentType(contentType) + c.Request().SetBody([]byte(body)) + c.Request().Header.SetContentLength(len(body)) + require.Equal(t, nil, c.Bind().Body(r)) + require.Equal(t, "body_param", r.BodyParam) + } + + testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) + testDecodeParser(MIMEApplicationXML, `body_param`) + testDecodeParser(MIMEApplicationForm, "body_param=body_param") + testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--") } diff --git a/binder/binder.go b/binder/binder.go index d393179011..03b5935add 100644 --- a/binder/binder.go +++ b/binder/binder.go @@ -1,6 +1,8 @@ package binder -import "errors" +import ( + "errors" +) // Binder errors var ( diff --git a/binder/cookie.go b/binder/cookie.go index e761e4776c..f5550d7aef 100644 --- a/binder/cookie.go +++ b/binder/cookie.go @@ -4,7 +4,7 @@ import ( "reflect" "strings" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) diff --git a/binder/form.go b/binder/form.go index 24983ccdea..610ebed4ba 100644 --- a/binder/form.go +++ b/binder/form.go @@ -4,7 +4,7 @@ import ( "reflect" "strings" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) diff --git a/binder/header.go b/binder/header.go index 688a81136a..c94c21cb64 100644 --- a/binder/header.go +++ b/binder/header.go @@ -4,7 +4,7 @@ import ( "reflect" "strings" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) diff --git a/binder/json.go b/binder/json.go index 570a7f9b79..b3ae2e8965 100644 --- a/binder/json.go +++ b/binder/json.go @@ -1,7 +1,7 @@ package binder import ( - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) type jsonBinding struct{} diff --git a/binder/mapping.go b/binder/mapping.go index bec5634808..b8ffea3e96 100644 --- a/binder/mapping.go +++ b/binder/mapping.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/gofiber/fiber/v3/internal/schema" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/bytebufferpool" ) @@ -25,22 +25,23 @@ type ParserType struct { Converter func(string) reflect.Value } -// decoderPool helps to improve BodyParser's, QueryParser's and ReqHeaderParser's performance -var decoderPool = &sync.Pool{New: func() any { - return decoderBuilder(ParserConfig{ - IgnoreUnknownKeys: true, - ZeroEmpty: true, - }) -}} +var ( + // decoderPoolMap helps to improve binders + decoderPoolMap = map[string]*sync.Pool{} + // tags is used to classify parser's pool + tags = []string{HeaderBinder.Name(), RespHeaderBinder.Name(), CookieBinder.Name(), QueryBinder.Name(), FormBinder.Name(), URIBinder.Name()} +) // SetParserDecoder allow globally change the option of form decoder, update decoderPool func SetParserDecoder(parserConfig ParserConfig) { - decoderPool = &sync.Pool{New: func() any { - return decoderBuilder(parserConfig) - }} + for _, tag := range tags { + decoderPoolMap[tag] = &sync.Pool{New: func() interface{} { + return decoderBuilder(parserConfig) + }} + } } -func decoderBuilder(parserConfig ParserConfig) any { +func decoderBuilder(parserConfig ParserConfig) interface{} { decoder := schema.NewDecoder() decoder.IgnoreUnknownKeys(parserConfig.IgnoreUnknownKeys) if parserConfig.SetAliasTag != "" { @@ -53,6 +54,17 @@ func decoderBuilder(parserConfig ParserConfig) any { return decoder } +func init() { + for _, tag := range tags { + decoderPoolMap[tag] = &sync.Pool{New: func() interface{} { + return decoderBuilder(ParserConfig{ + IgnoreUnknownKeys: true, + ZeroEmpty: true, + }) + }} + } +} + // parse data into the map or struct func parse(aliasTag string, out any, data map[string][]string) error { ptrVal := reflect.ValueOf(out) @@ -72,10 +84,10 @@ func parse(aliasTag string, out any, data map[string][]string) error { } // Parse data into the struct with gorilla/schema -func parseToStruct(aliasTag string, out any, data map[string][]string) error { +func parseToStruct(aliasTag string, out interface{}, data map[string][]string) error { // Get decoder from pool - schemaDecoder := decoderPool.Get().(*schema.Decoder) - defer decoderPool.Put(schemaDecoder) + schemaDecoder := decoderPoolMap[aliasTag].Get().(*schema.Decoder) + defer decoderPoolMap[aliasTag].Put(schemaDecoder) // Set alias tag schemaDecoder.SetAliasTag(aliasTag) diff --git a/binder/mapping_test.go b/binder/mapping_test.go index 2c5d275b8b..aec91ff2be 100644 --- a/binder/mapping_test.go +++ b/binder/mapping_test.go @@ -4,28 +4,28 @@ import ( "reflect" "testing" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func Test_EqualFieldType(t *testing.T) { var out int - utils.AssertEqual(t, false, equalFieldType(&out, reflect.Int, "key")) + require.False(t, equalFieldType(&out, reflect.Int, "key")) var dummy struct{ f string } - utils.AssertEqual(t, false, equalFieldType(&dummy, reflect.String, "key")) + require.False(t, equalFieldType(&dummy, reflect.String, "key")) var dummy2 struct{ f string } - utils.AssertEqual(t, false, equalFieldType(&dummy2, reflect.String, "f")) + require.False(t, equalFieldType(&dummy2, reflect.String, "f")) var user struct { Name string Address string `query:"address"` Age int `query:"AGE"` } - utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "name")) - utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Name")) - utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "address")) - utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Address")) - utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "AGE")) - utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "age")) + require.True(t, equalFieldType(&user, reflect.String, "name")) + require.True(t, equalFieldType(&user, reflect.String, "Name")) + require.True(t, equalFieldType(&user, reflect.String, "address")) + require.True(t, equalFieldType(&user, reflect.String, "Address")) + require.True(t, equalFieldType(&user, reflect.Int, "AGE")) + require.True(t, equalFieldType(&user, reflect.Int, "age")) } diff --git a/binder/query.go b/binder/query.go index ce62e09d0f..fe722c4147 100644 --- a/binder/query.go +++ b/binder/query.go @@ -4,7 +4,7 @@ import ( "reflect" "strings" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) diff --git a/binder/resp_header.go b/binder/resp_header.go index 2b31710d24..823109a6f0 100644 --- a/binder/resp_header.go +++ b/binder/resp_header.go @@ -4,7 +4,7 @@ import ( "reflect" "strings" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) diff --git a/client.go b/client.go index 9a8a74758a..dc18f14289 100644 --- a/client.go +++ b/client.go @@ -16,7 +16,7 @@ import ( "sync" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) @@ -565,7 +565,8 @@ func (a *Agent) SendFile(filename string, fieldname ...string) *Agent { // SendFiles reads files and appends them to multipart form request. // // Examples: -// SendFile("/path/to/file1", "fieldname1", "/path/to/file2") +// +// SendFile("/path/to/file1", "fieldname1", "/path/to/file2") func (a *Agent) SendFiles(filenamesAndFieldnames ...string) *Agent { pairs := len(filenamesAndFieldnames) if pairs&1 == 1 { @@ -737,16 +738,16 @@ func (a *Agent) RetryIf(retryIf RetryIfFunc) *Agent { } /************************** End Agent Setting **************************/ -var warnOnce sync.Once // Bytes returns the status code, bytes body and errors of url. +// +// it's not safe to use Agent after calling [Agent.Bytes] func (a *Agent) Bytes() (code int, body []byte, errs []error) { - warnOnce.Do(func() { - fmt.Println("[Warning] client is still in beta, API might change in the future!") - }) - defer a.release() + return a.bytes() +} +func (a *Agent) bytes() (code int, body []byte, errs []error) { if errs = append(errs, a.errs...); len(errs) > 0 { return } @@ -805,19 +806,29 @@ func printDebugInfo(req *Request, resp *Response, w io.Writer) { } // String returns the status code, string body and errors of url. +// +// it's not safe to use Agent after calling [Agent.String] func (a *Agent) String() (int, string, []error) { - code, body, errs := a.Bytes() + defer a.release() + code, body, errs := a.bytes() return code, utils.UnsafeString(body), errs } // Struct returns the status code, bytes body and errors of url. // And bytes body will be unmarshalled to given v. +// +// it's not safe to use Agent after calling [Agent.Struct] func (a *Agent) Struct(v any) (code int, body []byte, errs []error) { - if code, body, errs = a.Bytes(); len(errs) > 0 { + defer a.release() + if code, body, errs = a.bytes(); len(errs) > 0 { return } + if a.jsonDecoder == nil { + a.jsonDecoder = json.Unmarshal + } + if err := a.jsonDecoder(body, v); err != nil { errs = append(errs, err) } @@ -859,8 +870,12 @@ func (a *Agent) reset() { } var ( - clientPool sync.Pool - agentPool sync.Pool + clientPool sync.Pool + agentPool = sync.Pool{ + New: func() interface{} { + return &Agent{req: &Request{}} + }, + } responsePool sync.Pool argsPool sync.Pool formFilePool sync.Pool @@ -898,11 +913,7 @@ func ReleaseClient(c *Client) { // no longer needed. This allows Agent recycling, reduces GC pressure // and usually improves performance. func AcquireAgent() *Agent { - v := agentPool.Get() - if v == nil { - return &Agent{req: &Request{}} - } - return v.(*Agent) + return agentPool.Get().(*Agent) } // ReleaseAgent returns a acquired via AcquireAgent to Agent pool. diff --git a/client_test.go b/client_test.go index e0c682f8c6..3b95a94da7 100644 --- a/client_test.go +++ b/client_test.go @@ -19,7 +19,7 @@ import ( "encoding/json" "github.com/gofiber/fiber/v3/internal/tlstest" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp/fasthttputil" ) @@ -28,13 +28,17 @@ func Test_Client_Invalid_URL(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { - return c.SendString(c.Hostname()) + return c.SendString(c.Host()) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() a := Get("http://example.com\r\n\r\nGET /\r\n\r\n") @@ -42,9 +46,9 @@ func Test_Client_Invalid_URL(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "missing required Host header in request", errs[0].Error()) + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, "missing required Host header in request", errs[0].Error()) } func Test_Client_Unsupported_Protocol(t *testing.T) { @@ -54,10 +58,11 @@ func Test_Client_Unsupported_Protocol(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, `unsupported protocol "ftp". http and https are supported`, + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, `unsupported protocol "ftp". http and https are supported`, errs[0].Error()) + } func Test_Client_Get(t *testing.T) { @@ -65,13 +70,17 @@ func Test_Client_Get(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { - return c.SendString(c.Hostname()) + return c.SendString(c.Host()) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { a := Get("http://example.com") @@ -80,9 +89,9 @@ func Test_Client_Get(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "example.com", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "example.com", body) + require.Equal(t, 0, len(errs)) } } @@ -91,14 +100,17 @@ func Test_Client_Head(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() - app.Get("/", func(c Ctx) error { - return c.SendString(c.Hostname()) + app.Head("/", func(c Ctx) error { + return c.SendStatus(StatusAccepted) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() - + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { a := Head("http://example.com") @@ -106,9 +118,9 @@ func Test_Client_Head(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusAccepted, code) + require.Equal(t, "", body) + require.Equal(t, 0, len(errs)) } } @@ -117,14 +129,18 @@ func Test_Client_Post(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Post("/", func(c Ctx) error { return c.Status(StatusCreated). SendString(c.FormValue("foo")) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { args := AcquireArgs() @@ -138,9 +154,9 @@ func Test_Client_Post(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusCreated, code) - utils.AssertEqual(t, "bar", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusCreated, code) + require.Equal(t, "bar", body) + require.Equal(t, 0, len(errs)) ReleaseArgs(args) } @@ -151,13 +167,17 @@ func Test_Client_Put(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Put("/", func(c Ctx) error { return c.SendString(c.FormValue("foo")) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { args := AcquireArgs() @@ -171,9 +191,9 @@ func Test_Client_Put(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "bar", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "bar", body) + require.Equal(t, 0, len(errs)) ReleaseArgs(args) } @@ -184,13 +204,17 @@ func Test_Client_Patch(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Patch("/", func(c Ctx) error { return c.SendString(c.FormValue("foo")) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { args := AcquireArgs() @@ -204,9 +228,9 @@ func Test_Client_Patch(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "bar", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "bar", body) + require.Equal(t, 0, len(errs)) ReleaseArgs(args) } @@ -217,14 +241,18 @@ func Test_Client_Delete(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Delete("/", func(c Ctx) error { return c.Status(StatusNoContent). SendString("deleted") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { args := AcquireArgs() @@ -235,9 +263,9 @@ func Test_Client_Delete(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusNoContent, code) - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusNoContent, code) + require.Equal(t, "", body) + require.Equal(t, 0, len(errs)) ReleaseArgs(args) } @@ -248,13 +276,17 @@ func Test_Client_UserAgent(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.Send(c.Request().Header.UserAgent()) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() t.Run("default", func(t *testing.T) { for i := 0; i < 5; i++ { @@ -264,9 +296,9 @@ func Test_Client_UserAgent(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, defaultUserAgent, body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, defaultUserAgent, body) + require.Equal(t, 0, len(errs)) } }) @@ -281,9 +313,9 @@ func Test_Client_UserAgent(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "ua", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "ua", body) + require.Equal(t, 0, len(errs)) ReleaseClient(c) } }) @@ -390,27 +422,31 @@ func Test_Client_Agent_Host(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { - return c.SendString(c.Hostname()) + return c.SendString(c.Host()) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() a := Get("http://1.1.1.1:8080"). Host("example.com"). HostBytes([]byte("example.com")) - utils.AssertEqual(t, "1.1.1.1:8080", a.HostClient.Addr) + require.Equal(t, "1.1.1.1:8080", a.HostClient.Addr) a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "example.com", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "example.com", body) + require.Equal(t, 0, len(errs)) } func Test_Client_Agent_QueryString(t *testing.T) { @@ -432,7 +468,7 @@ func Test_Client_Agent_BasicAuth(t *testing.T) { auth := c.Get(HeaderAuthorization) // Decode the header contents raw, err := base64.StdEncoding.DecodeString(auth[6:]) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) return c.Send(raw) } @@ -486,13 +522,17 @@ func Test_Client_Agent_Custom_Response(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.SendString("custom") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { a := AcquireAgent() @@ -502,17 +542,17 @@ func Test_Client_Agent_Custom_Response(t *testing.T) { req.Header.SetMethod(MethodGet) req.SetRequestURI("http://example.com") - utils.AssertEqual(t, nil, a.Parse()) + require.Nil(t, a.Parse()) a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } code, body, errs := a.SetResponse(resp). String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "custom", body) - utils.AssertEqual(t, "custom", string(resp.Body())) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "custom", body) + require.Equal(t, "custom", string(resp.Body())) + require.Equal(t, 0, len(errs)) ReleaseResponse(resp) } @@ -523,13 +563,17 @@ func Test_Client_Agent_Dest(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.SendString("dest") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() t.Run("small dest", func(t *testing.T) { dest := []byte("de") @@ -540,10 +584,10 @@ func Test_Client_Agent_Dest(t *testing.T) { code, body, errs := a.Dest(dest[:0]).String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "dest", body) - utils.AssertEqual(t, "de", string(dest)) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "dest", body) + require.Equal(t, "de", string(dest)) + require.Equal(t, 0, len(errs)) }) t.Run("enough dest", func(t *testing.T) { @@ -555,10 +599,10 @@ func Test_Client_Agent_Dest(t *testing.T) { code, body, errs := a.Dest(dest[:0]).String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "dest", body) - utils.AssertEqual(t, "destar", string(dest)) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "dest", body) + require.Equal(t, "destar", string(dest)) + require.Equal(t, 0, len(errs)) }) } @@ -591,9 +635,13 @@ func Test_Client_Agent_RetryIf(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() a := Post("http://example.com"). RetryIf(func(req *Request) bool { @@ -618,13 +666,13 @@ func Test_Client_Agent_RetryIf(t *testing.T) { } _, _, errs := a.String() - utils.AssertEqual(t, dialsCount, 4) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, dialsCount, 4) + require.Equal(t, 0, len(errs)) } func Test_Client_Agent_Json(t *testing.T) { handler := func(c Ctx) error { - utils.AssertEqual(t, MIMEApplicationJSON, string(c.Request().Header.ContentType())) + require.Equal(t, MIMEApplicationJSON, string(c.Request().Header.ContentType())) return c.Send(c.Request().Body()) } @@ -643,14 +691,14 @@ func Test_Client_Agent_Json_Error(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "json: unsupported type: complex128", errs[0].Error()) + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, "json: unsupported type: complex128", errs[0].Error()) } func Test_Client_Agent_XML(t *testing.T) { handler := func(c Ctx) error { - utils.AssertEqual(t, MIMEApplicationXML, string(c.Request().Header.ContentType())) + require.Equal(t, MIMEApplicationXML, string(c.Request().Header.ContentType())) return c.Send(c.Request().Body()) } @@ -668,14 +716,14 @@ func Test_Client_Agent_XML_Error(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "xml: unsupported type: complex128", errs[0].Error()) + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, "xml: unsupported type: complex128", errs[0].Error()) } func Test_Client_Agent_Form(t *testing.T) { handler := func(c Ctx) error { - utils.AssertEqual(t, MIMEApplicationForm, string(c.Request().Header.ContentType())) + require.Equal(t, MIMEApplicationForm, string(c.Request().Header.ContentType())) return c.Send(c.Request().Body()) } @@ -698,19 +746,23 @@ func Test_Client_Agent_MultipartForm(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Post("/", func(c Ctx) error { - utils.AssertEqual(t, "multipart/form-data; boundary=myBoundary", c.Get(HeaderContentType)) + require.Equal(t, "multipart/form-data; boundary=myBoundary", c.Get(HeaderContentType)) mf, err := c.MultipartForm() - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "bar", mf.Value["foo"][0]) + require.NoError(t, err) + require.Equal(t, "bar", mf.Value["foo"][0]) return c.Send(c.Request().Body()) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() args := AcquireArgs() @@ -724,9 +776,9 @@ func Test_Client_Agent_MultipartForm(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "--myBoundary\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n--myBoundary--\r\n", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "--myBoundary\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n--myBoundary--\r\n", body) + require.Equal(t, 0, len(errs)) ReleaseArgs(args) } @@ -744,7 +796,7 @@ func Test_Client_Agent_MultipartForm_Errors(t *testing.T) { a.FileData(ff1, ff2). MultipartForm(args) - utils.AssertEqual(t, 4, len(a.errs)) + require.Equal(t, 4, len(a.errs)) ReleaseArgs(args) } @@ -753,34 +805,38 @@ func Test_Client_Agent_MultipartForm_SendFiles(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Post("/", func(c Ctx) error { - utils.AssertEqual(t, "multipart/form-data; boundary=myBoundary", c.Get(HeaderContentType)) + require.Equal(t, "multipart/form-data; boundary=myBoundary", c.Get(HeaderContentType)) fh1, err := c.FormFile("field1") - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fh1.Filename, "name") + require.NoError(t, err) + require.Equal(t, fh1.Filename, "name") buf := make([]byte, fh1.Size) f, err := fh1.Open() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer func() { _ = f.Close() }() _, err = f.Read(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "form file", string(buf)) + require.NoError(t, err) + require.Equal(t, "form file", string(buf)) fh2, err := c.FormFile("index") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) checkFormFile(t, fh2, ".github/testdata/index.html") fh3, err := c.FormFile("file3") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) checkFormFile(t, fh3, ".github/testdata/index.tmpl") return c.SendString("multipart form files") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() for i := 0; i < 5; i++ { ff := AcquireFormFile() @@ -798,9 +854,9 @@ func Test_Client_Agent_MultipartForm_SendFiles(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "multipart form files", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "multipart form files", body) + require.Equal(t, 0, len(errs)) ReleaseFormFile(ff) } @@ -810,18 +866,18 @@ func checkFormFile(t *testing.T, fh *multipart.FileHeader, filename string) { t.Helper() basename := filepath.Base(filename) - utils.AssertEqual(t, fh.Filename, basename) + require.Equal(t, fh.Filename, basename) b1, err := os.ReadFile(filename) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) b2 := make([]byte, fh.Size) f, err := fh.Open() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer func() { _ = f.Close() }() _, err = f.Read(b2) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, b1, b2) + require.NoError(t, err) + require.Equal(t, b1, b2) } func Test_Client_Agent_Multipart_Random_Boundary(t *testing.T) { @@ -832,7 +888,7 @@ func Test_Client_Agent_Multipart_Random_Boundary(t *testing.T) { reg := regexp.MustCompile(`multipart/form-data; boundary=\w{30}`) - utils.AssertEqual(t, true, reg.Match(a.req.Header.Peek(HeaderContentType))) + require.True(t, reg.Match(a.req.Header.Peek(HeaderContentType))) } func Test_Client_Agent_Multipart_Invalid_Boundary(t *testing.T) { @@ -842,8 +898,8 @@ func Test_Client_Agent_Multipart_Invalid_Boundary(t *testing.T) { Boundary("*"). MultipartForm(nil) - utils.AssertEqual(t, 1, len(a.errs)) - utils.AssertEqual(t, "mime: invalid boundary character", a.errs[0].Error()) + require.Equal(t, 1, len(a.errs)) + require.Equal(t, "mime: invalid boundary character", a.errs[0].Error()) } func Test_Client_Agent_SendFile_Error(t *testing.T) { @@ -852,8 +908,8 @@ func Test_Client_Agent_SendFile_Error(t *testing.T) { a := Post("http://example.com"). SendFile("non-exist-file!", "") - utils.AssertEqual(t, 1, len(a.errs)) - utils.AssertEqual(t, true, strings.Contains(a.errs[0].Error(), "open non-exist-file!")) + require.Equal(t, 1, len(a.errs)) + require.True(t, strings.Contains(a.errs[0].Error(), "open non-exist-file!")) } func Test_Client_Debug(t *testing.T) { @@ -871,12 +927,12 @@ func Test_Client_Debug(t *testing.T) { str := output.String() - utils.AssertEqual(t, true, strings.Contains(str, "Connected to example.com(pipe)")) - utils.AssertEqual(t, true, strings.Contains(str, "GET / HTTP/1.1")) - utils.AssertEqual(t, true, strings.Contains(str, "User-Agent: fiber")) - utils.AssertEqual(t, true, strings.Contains(str, "Host: example.com\r\n\r\n")) - utils.AssertEqual(t, true, strings.Contains(str, "HTTP/1.1 200 OK")) - utils.AssertEqual(t, true, strings.Contains(str, "Content-Type: text/plain; charset=utf-8\r\nContent-Length: 5\r\n\r\ndebug")) + require.True(t, strings.Contains(str, "Connected to example.com(pipe)")) + require.True(t, strings.Contains(str, "GET / HTTP/1.1")) + require.True(t, strings.Contains(str, "User-Agent: fiber")) + require.True(t, strings.Contains(str, "Host: example.com\r\n\r\n")) + require.True(t, strings.Contains(str, "HTTP/1.1 200 OK")) + require.True(t, strings.Contains(str, "Content-Type: text/plain; charset=utf-8\r\nContent-Length: 5\r\n\r\ndebug")) } func Test_Client_Agent_Timeout(t *testing.T) { @@ -884,14 +940,18 @@ func Test_Client_Agent_Timeout(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { time.Sleep(time.Millisecond * 200) return c.SendString("timeout") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() a := Get("http://example.com"). Timeout(time.Millisecond * 50) @@ -900,9 +960,9 @@ func Test_Client_Agent_Timeout(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "timeout", errs[0].Error()) + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, "timeout", errs[0].Error()) } func Test_Client_Agent_Reuse(t *testing.T) { @@ -910,13 +970,17 @@ func Test_Client_Agent_Reuse(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.SendString("reuse") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() a := Get("http://example.com"). Reuse() @@ -925,76 +989,84 @@ func Test_Client_Agent_Reuse(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "reuse", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "reuse", body) + require.Equal(t, 0, len(errs)) code, body, errs = a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "reuse", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "reuse", body) + require.Equal(t, 0, len(errs)) } func Test_Client_Agent_InsecureSkipVerify(t *testing.T) { t.Parallel() cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) serverTLSConf := &tls.Config{ Certificates: []tls.Certificate{cer}, } ln, err := net.Listen(NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.SendString("ignore tls") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() code, body, errs := Get("https://" + ln.Addr().String()). InsecureSkipVerify(). InsecureSkipVerify(). String() - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "ignore tls", body) + require.Equal(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "ignore tls", body) } func Test_Client_Agent_TLS(t *testing.T) { t.Parallel() serverTLSConf, clientTLSConf, err := tlstest.GetTLSConfigs() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln, err := net.Listen(NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.SendString("tls") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() code, body, errs := Get("https://" + ln.Addr().String()). TLSConfig(clientTLSConf). String() - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, "tls", body) + require.Equal(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, "tls", body) } func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { @@ -1002,19 +1074,23 @@ func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { if c.Request().URI().QueryArgs().Has("foo") { - return c.Redirect("/foo") + return c.Redirect().To("/foo") } - return c.Redirect("/") + return c.Redirect().To("/") }) app.Get("/foo", func(c Ctx) error { return c.SendString("redirect") }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() t.Run("success", func(t *testing.T) { a := Get("http://example.com?foo"). @@ -1024,9 +1100,9 @@ func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { code, body, errs := a.String() - utils.AssertEqual(t, 200, code) - utils.AssertEqual(t, "redirect", body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, 200, code) + require.Equal(t, "redirect", body) + require.Equal(t, 0, len(errs)) }) t.Run("error", func(t *testing.T) { @@ -1037,9 +1113,9 @@ func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { _, body, errs := a.String() - utils.AssertEqual(t, "", body) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "too many redirects detected when doing the request", errs[0].Error()) + require.Equal(t, "", body) + require.Equal(t, 1, len(errs)) + require.Equal(t, "too many redirects detected when doing the request", errs[0].Error()) }) } @@ -1048,7 +1124,7 @@ func Test_Client_Agent_Struct(t *testing.T) { ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", func(c Ctx) error { return c.JSON(data{true}) @@ -1058,7 +1134,11 @@ func Test_Client_Agent_Struct(t *testing.T) { return c.SendString(`{"success"`) }) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() t.Run("success", func(t *testing.T) { t.Parallel() @@ -1071,10 +1151,10 @@ func Test_Client_Agent_Struct(t *testing.T) { code, body, errs := a.Struct(&d) - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, `{"success":true}`, string(body)) - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, true, d.Success) + require.Equal(t, StatusOK, code) + require.Equal(t, `{"success":true}`, string(body)) + require.Equal(t, 0, len(errs)) + require.True(t, d.Success) }) t.Run("pre error", func(t *testing.T) { @@ -1087,10 +1167,10 @@ func Test_Client_Agent_Struct(t *testing.T) { var d data _, body, errs := a.Struct(&d) - utils.AssertEqual(t, "", string(body)) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "pre errors", errs[0].Error()) - utils.AssertEqual(t, false, d.Success) + require.Equal(t, "", string(body)) + require.Equal(t, 1, len(errs)) + require.Equal(t, "pre errors", errs[0].Error()) + require.False(t, d.Success) }) t.Run("error", func(t *testing.T) { @@ -1102,10 +1182,28 @@ func Test_Client_Agent_Struct(t *testing.T) { code, body, errs := a.JSONDecoder(json.Unmarshal).Struct(&d) - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, `{"success"`, string(body)) - utils.AssertEqual(t, 1, len(errs)) - utils.AssertEqual(t, "unexpected end of JSON input", errs[0].Error()) + require.Equal(t, StatusOK, code) + require.Equal(t, `{"success"`, string(body)) + require.Equal(t, 1, len(errs)) + require.Equal(t, "unexpected end of JSON input", errs[0].Error()) + }) + + t.Run("nil jsonDecoder", func(t *testing.T) { + a := AcquireAgent() + defer ReleaseAgent(a) + defer a.ConnectionClose() + request := a.Request() + request.Header.SetMethod("GET") + request.SetRequestURI("http://example.com") + err := a.Parse() + require.NoError(t, err) + a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } + var d data + code, body, errs := a.Struct(&d) + require.Equal(t, StatusOK, code) + require.Equal(t, `{"success":true}`, string(body)) + require.Equal(t, 0, len(errs)) + require.True(t, d.Success) }) } @@ -1114,12 +1212,12 @@ func Test_Client_Agent_Parse(t *testing.T) { a := Get("https://example.com:10443") - utils.AssertEqual(t, nil, a.Parse()) + require.Nil(t, a.Parse()) } func Test_AddMissingPort_TLS(t *testing.T) { addr := addMissingPort("example.com", true) - utils.AssertEqual(t, "example.com:443", addr) + require.Equal(t, "example.com:443", addr) } func testAgent(t *testing.T, handler Handler, wrapAgent func(agent *Agent), excepted string, count ...int) { @@ -1127,11 +1225,15 @@ func testAgent(t *testing.T, handler Handler, wrapAgent func(agent *Agent), exce ln := fasthttputil.NewInmemoryListener() - app := New(Config{DisableStartupMessage: true}) + app := New() app.Get("/", handler) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, nil, app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + })) + }() c := 1 if len(count) > 0 { @@ -1147,9 +1249,9 @@ func testAgent(t *testing.T, handler Handler, wrapAgent func(agent *Agent), exce code, body, errs := a.String() - utils.AssertEqual(t, StatusOK, code) - utils.AssertEqual(t, excepted, body) - utils.AssertEqual(t, 0, len(errs)) + require.Equal(t, StatusOK, code) + require.Equal(t, excepted, body) + require.Equal(t, 0, len(errs)) } } diff --git a/color.go b/color.go new file mode 100644 index 0000000000..cbccd2ebee --- /dev/null +++ b/color.go @@ -0,0 +1,107 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +// Colors is a struct to define custom colors for Fiber app and middlewares. +type Colors struct { + // Black color. + // + // Optional. Default: "\u001b[90m" + Black string + + // Red color. + // + // Optional. Default: "\u001b[91m" + Red string + + // Green color. + // + // Optional. Default: "\u001b[92m" + Green string + + // Yellow color. + // + // Optional. Default: "\u001b[93m" + Yellow string + + // Blue color. + // + // Optional. Default: "\u001b[94m" + Blue string + + // Magenta color. + // + // Optional. Default: "\u001b[95m" + Magenta string + + // Cyan color. + // + // Optional. Default: "\u001b[96m" + Cyan string + + // White color. + // + // Optional. Default: "\u001b[97m" + White string + + // Reset color. + // + // Optional. Default: "\u001b[0m" + Reset string +} + +// DefaultColors Default color codes +var DefaultColors = Colors{ + Black: "\u001b[90m", + Red: "\u001b[91m", + Green: "\u001b[92m", + Yellow: "\u001b[93m", + Blue: "\u001b[94m", + Magenta: "\u001b[95m", + Cyan: "\u001b[96m", + White: "\u001b[97m", + Reset: "\u001b[0m", +} + +// defaultColors is a function to override default colors to config +func defaultColors(colors Colors) Colors { + if colors.Black == "" { + colors.Black = DefaultColors.Black + } + + if colors.Red == "" { + colors.Red = DefaultColors.Red + } + + if colors.Green == "" { + colors.Green = DefaultColors.Green + } + + if colors.Yellow == "" { + colors.Yellow = DefaultColors.Yellow + } + + if colors.Blue == "" { + colors.Blue = DefaultColors.Blue + } + + if colors.Magenta == "" { + colors.Magenta = DefaultColors.Magenta + } + + if colors.Cyan == "" { + colors.Cyan = DefaultColors.Cyan + } + + if colors.White == "" { + colors.White = DefaultColors.White + } + + if colors.Reset == "" { + colors.Reset = DefaultColors.Reset + } + + return colors +} diff --git a/ctx.go b/ctx.go index 4a0e50d577..ae139ff390 100644 --- a/ctx.go +++ b/ctx.go @@ -7,8 +7,8 @@ package fiber import ( "bytes" "context" + "crypto/tls" "encoding/json" - "encoding/xml" "fmt" "io" "mime/multipart" @@ -21,7 +21,7 @@ import ( "text/template" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/savsgio/dictpool" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" @@ -52,6 +52,19 @@ type DefaultCtx struct { matched bool // Non use route matched viewBindMap *dictpool.Dict // Default view map to bind template engine bind *Bind // Default bind reference + redirect *Redirect // Default redirect reference + redirectionMessages []string // Messages of the previous redirect +} + +// TLSHandler object +type TLSHandler struct { + clientHelloInfo *tls.ClientHelloInfo +} + +// GetClientInfo Callback function to set CHI +func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + t.clientHelloInfo = info + return nil, nil } // Range data for c.Range @@ -97,9 +110,9 @@ func (c *DefaultCtx) Accepts(offers ...string) string { for len(header) > 0 && commaPos != -1 { commaPos = strings.IndexByte(header, ',') if commaPos != -1 { - spec = utils.Trim(header[:commaPos], ' ') + spec = strings.TrimLeft(header[:commaPos], " ") } else { - spec = utils.TrimLeft(header, ' ') + spec = strings.TrimLeft(header, " ") } if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 { spec = spec[:factorSign] @@ -199,7 +212,7 @@ func (c *DefaultCtx) BaseURL() string { if c.baseURI != "" { return c.baseURI } - c.baseURI = c.Scheme() + "://" + c.Hostname() + c.baseURI = c.Scheme() + "://" + c.Host() return c.baseURI } @@ -304,7 +317,7 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) { fasthttp.ReleaseCookie(fcookie) } -// Cookies is used for getting a cookie value by key. +// Cookies are used for getting a cookie value by key. // Defaults to the empty string "" if the cookie doesn't exist. // If a default value is given, it will return that value if the cookie doesn't exist. // The returned value is only valid within the handler. Do not store any references. @@ -370,12 +383,7 @@ func (c *DefaultCtx) Format(body any) error { case "txt": return c.SendString(b) case "xml": - raw, err := xml.Marshal(body) - if err != nil { - return fmt.Errorf("error serializing xml: %v", body) - } - c.fasthttp.Response.SetBody(raw) - return nil + return c.XML(body) } return c.SendString(b) } @@ -462,19 +470,33 @@ func (c *DefaultCtx) GetRespHeader(key string, defaultValue ...string) string { return defaultString(c.app.getString(c.fasthttp.Response.Header.Peek(key)), defaultValue) } -// Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header. +// Host contains the host derived from the X-Forwarded-Host or Host HTTP header. // Returned value is only valid within the handler. Do not store any references. // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. -func (c *DefaultCtx) Hostname() string { +func (c *DefaultCtx) Host() string { if c.IsProxyTrusted() { if host := c.Get(HeaderXForwardedHost); len(host) > 0 { + commaPos := strings.Index(host, ",") + if commaPos != -1 { + return host[:commaPos] + } return host } } return c.app.getString(c.fasthttp.Request.URI().Host()) } +// Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting instead. +// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +func (c *DefaultCtx) Hostname() string { + addr, _ := parseAddr(c.Host()) + + return addr +} + // Port returns the remote port of the request. func (c *DefaultCtx) Port() string { port := c.fasthttp.RemoteAddr().(*net.TCPAddr).Port @@ -482,33 +504,130 @@ func (c *DefaultCtx) Port() string { } // IP returns the remote IP address of the request. +// If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) IP() string { if c.IsProxyTrusted() && len(c.app.config.ProxyHeader) > 0 { - return c.Get(c.app.config.ProxyHeader) + return c.extractIPFromHeader(c.app.config.ProxyHeader) } return c.fasthttp.RemoteIP().String() } -// IPs returns an string slice of IP addresses specified in the X-Forwarded-For request header. -func (c *DefaultCtx) IPs() (ips []string) { - header := c.fasthttp.Request.Header.Peek(HeaderXForwardedFor) - if len(header) == 0 { - return +// extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. +// When IP validation is enabled, any invalid IPs will be omitted. +func (c *DefaultCtx) extractIPsFromHeader(header string) []string { + headerValue := c.Get(header) + + // We can't know how many IPs we will return, but we will try to guess with this constant division. + // Counting ',' makes function slower for about 50ns in general case. + estimatedCount := len(headerValue) / 8 + if estimatedCount > 8 { + estimatedCount = 8 // Avoid big allocation on big header } - ips = make([]string, bytes.Count(header, []byte(","))+1) - var commaPos, i int + + ipsFound := make([]string, 0, estimatedCount) + + i := 0 + j := -1 + +iploop: for { - commaPos = bytes.IndexByte(header, ',') - if commaPos != -1 { - ips[i] = utils.Trim(c.app.getString(header[:commaPos]), ' ') - header, i = header[commaPos+1:], i+1 - } else { - ips[i] = utils.Trim(c.app.getString(header), ' ') - return + v4 := false + v6 := false + + // Manually splitting string without allocating slice, working with parts directly + i, j = j+1, j+2 + + if j > len(headerValue) { + break } + + for j < len(headerValue) && headerValue[j] != ',' { + if headerValue[j] == ':' { + v6 = true + } else if headerValue[j] == '.' { + v4 = true + } + j++ + } + + for i < j && headerValue[i] == ' ' { + i++ + } + + s := strings.TrimRight(headerValue[i:j], " ") + + if c.app.config.EnableIPValidation { + // Skip validation if IP is clearly not IPv4/IPv6, otherwise validate without allocations + if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { + continue iploop + } + } + + ipsFound = append(ipsFound, s) } + + return ipsFound +} + +// extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. +// currently, it will return the first valid IP address in header. +// when IP validation is disabled, it will simply return the value of the header without any inspection. +// Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. +func (c *DefaultCtx) extractIPFromHeader(header string) string { + if c.app.config.EnableIPValidation { + headerValue := c.Get(header) + + i := 0 + j := -1 + + iploop: + for { + v4 := false + v6 := false + i, j = j+1, j+2 + + if j > len(headerValue) { + break + } + + for j < len(headerValue) && headerValue[j] != ',' { + if headerValue[j] == ':' { + v6 = true + } else if headerValue[j] == '.' { + v4 = true + } + j++ + } + + for i < j && headerValue[i] == ' ' { + i++ + } + + s := strings.TrimRight(headerValue[i:j], " ") + + if c.app.config.EnableIPValidation { + if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { + continue iploop + } + } + + return s + } + + return c.fasthttp.RemoteIP().String() + } + + // default behaviour if IP validation is not enabled is just to return whatever value is + // in the proxy header. Even if it is empty or invalid + return c.Get(c.app.config.ProxyHeader) +} + +// IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. +// When IP validation is enabled, only valid IPs are returned. +func (c *DefaultCtx) IPs() (ips []string) { + return c.extractIPsFromHeader(HeaderXForwardedFor) } // Is returns the matching content type, @@ -520,7 +639,7 @@ func (c *DefaultCtx) Is(extension string) bool { } return strings.HasPrefix( - utils.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), ' '), + strings.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), " "), extensionHeader, ) } @@ -560,10 +679,22 @@ func (c *DefaultCtx) JSONP(data any, callback ...string) error { result = cb + "(" + c.app.getString(raw) + ");" c.setCanonical(HeaderXContentTypeOptions, "nosniff") - c.fasthttp.Response.Header.SetContentType(MIMEApplicationJavaScriptCharsetUTF8) + c.fasthttp.Response.Header.SetContentType(MIMETextJavaScriptCharsetUTF8) return c.SendString(result) } +// XML converts any interface or string to XML. +// This method also sets the content header to application/xml. +func (c *DefaultCtx) XML(data any) error { + raw, err := c.app.config.XMLEncoder(data) + if err != nil { + return err + } + c.fasthttp.Response.SetBodyRaw(raw) + c.fasthttp.Response.Header.SetContentType(MIMEApplicationXML) + return nil +} + // Links joins the links followed by the property to populate the response's Link HTTP header field. func (c *DefaultCtx) Links(link ...string) { if len(link) == 0 { @@ -579,13 +710,13 @@ func (c *DefaultCtx) Links(link ...string) { _, _ = bb.WriteString(`; rel="` + link[i] + `",`) } } - c.setCanonical(HeaderLink, utils.TrimRight(c.app.getString(bb.Bytes()), ',')) + c.setCanonical(HeaderLink, strings.TrimRight(c.app.getString(bb.Bytes()), ",")) bytebufferpool.Put(bb) } -// Locals makes it possible to pass any values under string keys scoped to the request +// Locals makes it possible to pass any values under keys scoped to the request // and therefore available to all following routes that match the request. -func (c *DefaultCtx) Locals(key string, value ...any) (val any) { +func (c *DefaultCtx) Locals(key any, value ...any) (val any) { if len(value) == 0 { return c.fasthttp.UserValue(key) } @@ -618,6 +749,15 @@ func (c *DefaultCtx) MultipartForm() (*multipart.Form, error) { return c.fasthttp.MultipartForm() } +// ClientHelloInfo return CHI from context +func (c *DefaultCtx) ClientHelloInfo() *tls.ClientHelloInfo { + if c.app.tlsHandler != nil { + return c.app.tlsHandler.clientHelloInfo + } + + return nil +} + // Next executes the next method in the stack that matches the current route. func (c *DefaultCtx) Next() (err error) { // Increment handler index @@ -628,7 +768,11 @@ func (c *DefaultCtx) Next() (err error) { err = c.route.Handlers[c.indexHandler](c) } else { // Continue handler stack - _, err = c.app.next(c, c.app.newCtxFunc != nil) + if c.app.newCtxFunc != nil { + _, err = c.app.nextCustom(c) + } else { + _, err = c.app.next(c) + } } return err } @@ -636,8 +780,14 @@ func (c *DefaultCtx) Next() (err error) { // RestartRouting instead of going to the next handler. This may be usefull after // changing the request path. Note that handlers might be executed again. func (c *DefaultCtx) RestartRouting() error { + var err error + c.indexRoute = -1 - _, err := c.app.next(c, c.app.newCtxFunc != nil) + if c.app.newCtxFunc != nil { + _, err = c.app.nextCustom(c) + } else { + _, err = c.app.next(c) + } return err } @@ -661,7 +811,7 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { if len(key) != len(c.route.Params[i]) { continue } - if c.route.Params[i] == key { + if c.route.Params[i] == key || (!c.app.config.CaseSensitive && utils.EqualFold(c.route.Params[i], key)) { // in case values are not here if len(c.values) <= i || len(c.values[i]) == 0 { break @@ -720,10 +870,21 @@ func (c *DefaultCtx) Scheme() string { if len(key) < 12 { return // X-Forwarded- } else if bytes.HasPrefix(key, []byte("X-Forwarded-")) { + v := c.app.getString(val) if bytes.Equal(key, []byte(HeaderXForwardedProto)) { - scheme = c.app.getString(val) + commaPos := strings.Index(v, ",") + if commaPos != -1 { + scheme = v[:commaPos] + } else { + scheme = v + } } else if bytes.Equal(key, []byte(HeaderXForwardedProtocol)) { - scheme = c.app.getString(val) + commaPos := strings.Index(v, ",") + if commaPos != -1 { + scheme = v[:commaPos] + } else { + scheme = v + } } else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) { scheme = "https" } @@ -798,19 +959,20 @@ func (c *DefaultCtx) Range(size int) (rangeData Range, err error) { return } -// Redirect to the URL derived from the specified path, with specified status. +// Redirect returns the Redirect reference. +// Use Redirect().Status() to set custom redirection status code. // If status is not specified, status defaults to 302 Found. -func (c *DefaultCtx) Redirect(location string, status ...int) error { - c.setCanonical(HeaderLocation, location) - if len(status) > 0 { - c.Status(status[0]) - } else { - c.Status(StatusFound) +// You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. +func (c *DefaultCtx) Redirect() *Redirect { + if c.redirect == nil { + c.redirect = AcquireRedirect() + c.redirect.c = c } - return nil + + return c.redirect } -// Add vars to default view var map binding to template engine. +// Bind Add vars to default view var map binding to template engine. // Variables are read by the Render method and may be overwritten. func (c *DefaultCtx) BindVars(vars Map) error { // init viewBindMap - lazy map @@ -828,20 +990,24 @@ func (c *DefaultCtx) BindVars(vars Map) error { func (c *DefaultCtx) getLocationFromRoute(route Route, params Map) (string, error) { buf := bytebufferpool.Get() for _, segment := range route.routeParser.segs { + if !segment.IsParam { + _, err := buf.WriteString(segment.Const) + if err != nil { + return "", err + } + continue + } + for key, val := range params { - if segment.IsParam && (key == segment.ParamName || (segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters))) { + isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName)) + isGreedy := segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters) + if isSame || isGreedy { _, err := buf.WriteString(utils.ToString(val)) if err != nil { return "", err } } } - if !segment.IsParam { - _, err := buf.WriteString(segment.Const) - if err != nil { - return "", err - } - } } location := buf.String() // release buffer @@ -854,45 +1020,6 @@ func (c *DefaultCtx) GetRouteURL(routeName string, params Map) (string, error) { return c.getLocationFromRoute(c.App().GetRoute(routeName), params) } -// RedirectToRoute to the Route registered in the app with appropriate parameters -// If status is not specified, status defaults to 302 Found. -// If you want to send queries to route, you must add "queries" key typed as map[string]string to params. -func (c *DefaultCtx) RedirectToRoute(routeName string, params Map, status ...int) error { - location, err := c.getLocationFromRoute(c.App().GetRoute(routeName), params) - if err != nil { - return err - } - - // Check queries - if queries, ok := params["queries"].(map[string]string); ok { - queryText := bytebufferpool.Get() - defer bytebufferpool.Put(queryText) - - i := 1 - for k, v := range queries { - _, _ = queryText.WriteString(k + "=" + v) - - if i != len(queries) { - _, _ = queryText.WriteString("&") - } - i++ - } - - return c.Redirect(location+"?"+queryText.String(), status...) - } - return c.Redirect(location, status...) -} - -// RedirectBack to the URL to referer -// If status is not specified, status defaults to 302 Found. -func (c *DefaultCtx) RedirectBack(fallback string, status ...int) error { - location := c.Get(HeaderReferer) - if location == "" { - location = fallback - } - return c.Redirect(location, status...) -} - // Render a template with data and sends a text/html response. // We support the following engines: https://github.com/gofiber/template func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { @@ -901,11 +1028,13 @@ func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) - // Pass-locals-to-views & bind + // Pass-locals-to-views, bind, appListKeys c.renderExtensions(bind) - rendered := false - for prefix, app := range c.app.appList { + var rendered bool + for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- { + prefix := c.app.mountFields.appListKeys[i] + app := c.app.mountFields.appList[prefix] if prefix == "" || strings.Contains(c.OriginalURL(), prefix) { if len(layouts) == 0 && app.config.ViewsLayout != "" { layouts = []string{ @@ -969,6 +1098,10 @@ func (c *DefaultCtx) renderExtensions(bind Map) { } }) } + + if len(c.app.mountFields.appListKeys) == 0 { + c.app.generateAppListKeys() + } } // Route returns the matched Route struct. @@ -1124,7 +1257,6 @@ func (c *DefaultCtx) SendStream(stream io.Reader, size ...int) error { c.fasthttp.Response.SetBodyStream(stream, size[0]) } else { c.fasthttp.Response.SetBodyStream(stream, -1) - c.setCanonical(HeaderContentLength, strconv.Itoa(len(c.fasthttp.Response.Body()))) } return nil @@ -1146,7 +1278,7 @@ func (c *DefaultCtx) Subdomains(offset ...int) []string { if len(offset) > 0 { o = offset[0] } - subdomains := strings.Split(c.Hostname(), ".") + subdomains := strings.Split(c.Host(), ".") l := len(subdomains) - o // Check index to avoid slice bounds out of range panic if l < 0 { @@ -1218,7 +1350,7 @@ func (c *DefaultCtx) WriteString(s string) (int, error) { // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, // indicating that the request was issued by a client library (such as jQuery). func (c *DefaultCtx) XHR() bool { - return utils.EqualFoldBytes(utils.UnsafeBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest")) + return utils.EqualFold(c.Get(HeaderXRequestedWith), "xmlhttprequest") } // configDependentPaths set paths for route recognition and prepared paths for the user, @@ -1240,7 +1372,7 @@ func (c *DefaultCtx) configDependentPaths() { } // If StrictRouting is disabled, we strip all trailing slashes if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' { - c.detectionPathBuffer = utils.TrimRightBytes(c.detectionPathBuffer, '/') + c.detectionPathBuffer = bytes.TrimRight(c.detectionPathBuffer, "/") } c.detectionPath = c.app.getString(c.detectionPathBuffer) diff --git a/ctx_interface.go b/ctx_interface.go index b537bae8b9..6dccdcb30c 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -6,6 +6,7 @@ package fiber import ( "context" + "crypto/tls" "io" "mime/multipart" @@ -122,7 +123,13 @@ type Ctx interface { // Make copies or use the Immutable setting instead. GetRespHeader(key string, defaultValue ...string) string - // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header. + // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. + Host() string + + // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. // Returned value is only valid within the handler. Do not store any references. // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. @@ -154,12 +161,16 @@ type Ctx interface { // By default, the callback name is simply callback. JSONP(data any, callback ...string) error + // XML converts any interface or string to XML. + // This method also sets the content header to application/xml. + XML(data any) error + // Links joins the links followed by the property to populate the response's Link HTTP header field. Links(link ...string) // Locals makes it possible to pass any values under string keys scoped to the request // and therefore available to all following routes that match the request. - Locals(key string, value ...any) (val any) + Locals(key any, value ...any) (val any) // Location sets the response Location HTTP header to the specified path parameter. Location(path string) @@ -218,9 +229,11 @@ type Ctx interface { // Range returns a struct containing the type and a slice of ranges. Range(size int) (rangeData Range, err error) - // Redirect to the URL derived from the specified path, with specified status. + // Redirect returns the Redirect reference. + // Use Redirect().Status() to set custom redirection status code. // If status is not specified, status defaults to 302 Found. - Redirect(location string, status ...int) error + // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. + Redirect() *Redirect // Add vars to default view var map binding to template engine. // Variables are read by the Render method and may be overwritten. @@ -229,15 +242,6 @@ type Ctx interface { // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" GetRouteURL(routeName string, params Map) (string, error) - // RedirectToRoute to the Route registered in the app with appropriate parameters - // If status is not specified, status defaults to 302 Found. - // If you want to send queries to route, you must add "queries" key typed as map[string]string to params. - RedirectToRoute(routeName string, params Map, status ...int) error - - // RedirectBack to the URL to referer - // If status is not specified, status defaults to 302 Found. - RedirectBack(fallback string, status ...int) error - // Render a template with data and sends a text/html response. // We support the following engines: https://github.com/gofiber/template Render(name string, bind Map, layouts ...string) error @@ -329,6 +333,9 @@ type Ctx interface { // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser Bind() *Bind + // ClientHelloInfo return CHI from context + ClientHelloInfo() *tls.ClientHelloInfo + // SetReq resets fields of context that is relating to request. setReq(fctx *fasthttp.RequestCtx) @@ -431,8 +438,14 @@ func (c *DefaultCtx) release() { c.route = nil c.fasthttp = nil c.bind = nil + c.redirectionMessages = c.redirectionMessages[:0] if c.viewBindMap != nil { dictpool.ReleaseDict(c.viewBindMap) + c.viewBindMap = nil + } + if c.redirect != nil { + ReleaseRedirect(c.redirect) + c.redirect = nil } } diff --git a/ctx_test.go b/ctx_test.go index ee69eb4607..3ab3bdb19a 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -4,20 +4,18 @@ package fiber -// go test -v -run=^$ -bench=Benchmark_Ctx_Accepts -benchmem -count=4 -// go test -run Test_Ctx - import ( "bufio" "bytes" "compress/gzip" "context" + "crypto/tls" + "encoding/xml" "errors" "fmt" "io" "mime/multipart" "net/http/httptest" - "net/url" "os" "path/filepath" "strconv" @@ -27,7 +25,8 @@ import ( "time" "github.com/gofiber/fiber/v3/internal/storage/memory" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" ) @@ -39,24 +38,24 @@ func Test_Ctx_Accepts(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9") - utils.AssertEqual(t, "", c.Accepts("")) - utils.AssertEqual(t, "", c.Accepts()) - utils.AssertEqual(t, ".xml", c.Accepts(".xml")) - utils.AssertEqual(t, "", c.Accepts(".john")) + require.Equal(t, "", c.Accepts("")) + require.Equal(t, "", c.Accepts()) + require.Equal(t, ".xml", c.Accepts(".xml")) + require.Equal(t, "", c.Accepts(".john")) c.Request().Header.Set(HeaderAccept, "text/*, application/json") - utils.AssertEqual(t, "html", c.Accepts("html")) - utils.AssertEqual(t, "text/html", c.Accepts("text/html")) - utils.AssertEqual(t, "json", c.Accepts("json", "text")) - utils.AssertEqual(t, "application/json", c.Accepts("application/json")) - utils.AssertEqual(t, "", c.Accepts("image/png")) - utils.AssertEqual(t, "", c.Accepts("png")) + require.Equal(t, "html", c.Accepts("html")) + require.Equal(t, "text/html", c.Accepts("text/html")) + require.Equal(t, "json", c.Accepts("json", "text")) + require.Equal(t, "application/json", c.Accepts("application/json")) + require.Equal(t, "", c.Accepts("image/png")) + require.Equal(t, "", c.Accepts("png")) c.Request().Header.Set(HeaderAccept, "text/html, application/json") - utils.AssertEqual(t, "text/*", c.Accepts("text/*")) + require.Equal(t, "text/*", c.Accepts("text/*")) c.Request().Header.Set(HeaderAccept, "*/*") - utils.AssertEqual(t, "html", c.Accepts("html")) + require.Equal(t, "html", c.Accepts("html")) } // go test -v -run=^$ -bench=Benchmark_Ctx_Accepts -benchmem -count=4 @@ -71,7 +70,7 @@ func Benchmark_Ctx_Accepts(b *testing.B) { for n := 0; n < b.N; n++ { res = c.Accepts(".xml") } - utils.AssertEqual(b, ".xml", res) + require.Equal(b, ".xml", res) } type customCtx struct { @@ -98,10 +97,10 @@ func Test_Ctx_CustomCtx(t *testing.T) { return c.SendString(c.Params("id")) }) resp, err := app.Test(httptest.NewRequest("GET", "/v3", &bytes.Buffer{})) - utils.AssertEqual(t, nil, err, "app.Test(req)") + require.NoError(t, err, "app.Test(req)") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "io.ReadAll(resp.Body)") - utils.AssertEqual(t, "prefix_v3", string(body)) + require.NoError(t, err, "io.ReadAll(resp.Body)") + require.Equal(t, "prefix_v3", string(body)) } // go test -run Test_Ctx_Accepts_EmptyAccept @@ -110,7 +109,7 @@ func Test_Ctx_Accepts_EmptyAccept(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, ".forwarded", c.Accepts(".forwarded")) + require.Equal(t, ".forwarded", c.Accepts(".forwarded")) } // go test -run Test_Ctx_Accepts_Wildcard @@ -120,11 +119,11 @@ func Test_Ctx_Accepts_Wildcard(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderAccept, "*/*;q=0.9") - utils.AssertEqual(t, "html", c.Accepts("html")) - utils.AssertEqual(t, "foo", c.Accepts("foo")) - utils.AssertEqual(t, ".bar", c.Accepts(".bar")) + require.Equal(t, "html", c.Accepts("html")) + require.Equal(t, "foo", c.Accepts("foo")) + require.Equal(t, ".bar", c.Accepts(".bar")) c.Request().Header.Set(HeaderAccept, "text/html,application/*;q=0.9") - utils.AssertEqual(t, "xml", c.Accepts("xml")) + require.Equal(t, "xml", c.Accepts("xml")) } // go test -run Test_Ctx_AcceptsCharsets @@ -134,7 +133,7 @@ func Test_Ctx_AcceptsCharsets(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") - utils.AssertEqual(t, "utf-8", c.AcceptsCharsets("utf-8")) + require.Equal(t, "utf-8", c.AcceptsCharsets("utf-8")) } // go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsCharsets -benchmem -count=4 @@ -149,7 +148,7 @@ func Benchmark_Ctx_AcceptsCharsets(b *testing.B) { for n := 0; n < b.N; n++ { res = c.AcceptsCharsets("utf-8") } - utils.AssertEqual(b, "utf-8", res) + require.Equal(b, "utf-8", res) } // go test -run Test_Ctx_AcceptsEncodings @@ -159,8 +158,8 @@ func Test_Ctx_AcceptsEncodings(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5") - utils.AssertEqual(t, "gzip", c.AcceptsEncodings("gzip")) - utils.AssertEqual(t, "abc", c.AcceptsEncodings("abc")) + require.Equal(t, "gzip", c.AcceptsEncodings("gzip")) + require.Equal(t, "abc", c.AcceptsEncodings("abc")) } // go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsEncodings -benchmem -count=4 @@ -175,7 +174,7 @@ func Benchmark_Ctx_AcceptsEncodings(b *testing.B) { for n := 0; n < b.N; n++ { res = c.AcceptsEncodings("gzip") } - utils.AssertEqual(b, "gzip", res) + require.Equal(b, "gzip", res) } // go test -run Test_Ctx_AcceptsLanguages @@ -185,7 +184,7 @@ func Test_Ctx_AcceptsLanguages(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") - utils.AssertEqual(t, "fr", c.AcceptsLanguages("fr")) + require.Equal(t, "fr", c.AcceptsLanguages("fr")) } // go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsLanguages -benchmem -count=4 @@ -200,7 +199,7 @@ func Benchmark_Ctx_AcceptsLanguages(b *testing.B) { for n := 0; n < b.N; n++ { res = c.AcceptsLanguages("fr") } - utils.AssertEqual(b, "fr", res) + require.Equal(b, "fr", res) } // go test -run Test_Ctx_App @@ -210,7 +209,7 @@ func Test_Ctx_App(t *testing.T) { app.config.BodyLimit = 1000 c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, 1000, c.App().config.BodyLimit) + require.Equal(t, 1000, c.App().config.BodyLimit) } // go test -run Test_Ctx_Append @@ -242,11 +241,11 @@ func Test_Ctx_Append(t *testing.T) { // without append value c.Append("X-Custom-Header") - utils.AssertEqual(t, "Hello, World", string(c.Response().Header.Peek("X-Test"))) - utils.AssertEqual(t, "World, XHello, Hello", string(c.Response().Header.Peek("X2-Test"))) - utils.AssertEqual(t, "XHello, World, Hello", string(c.Response().Header.Peek("X3-Test"))) - utils.AssertEqual(t, "XHello, Hello, HelloZ, YHello", string(c.Response().Header.Peek("X4-Test"))) - utils.AssertEqual(t, "", string(c.Response().Header.Peek("x-custom-header"))) + require.Equal(t, "Hello, World", string(c.Response().Header.Peek("X-Test"))) + require.Equal(t, "World, XHello, Hello", string(c.Response().Header.Peek("X2-Test"))) + require.Equal(t, "XHello, World, Hello", string(c.Response().Header.Peek("X3-Test"))) + require.Equal(t, "XHello, Hello, HelloZ, YHello", string(c.Response().Header.Peek("X4-Test"))) + require.Equal(t, "", string(c.Response().Header.Peek("x-custom-header"))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4 @@ -261,7 +260,7 @@ func Benchmark_Ctx_Append(b *testing.B) { c.Append("X-Custom-Header", "World") c.Append("X-Custom-Header", "Hello") } - utils.AssertEqual(b, "Hello, World", app.getString(c.Response().Header.Peek("X-Custom-Header"))) + require.Equal(b, "Hello, World", app.getString(c.Response().Header.Peek("X-Custom-Header"))) } // go test -run Test_Ctx_Attachment @@ -272,14 +271,14 @@ func Test_Ctx_Attachment(t *testing.T) { // empty c.Attachment() - utils.AssertEqual(t, `attachment`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, `attachment`, string(c.Response().Header.Peek(HeaderContentDisposition))) // real filename c.Attachment("./static/img/logo.png") - utils.AssertEqual(t, `attachment; filename="logo.png"`, string(c.Response().Header.Peek(HeaderContentDisposition))) - utils.AssertEqual(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) + require.Equal(t, `attachment; filename="logo.png"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) // check quoting c.Attachment("another document.pdf\"\r\nBla: \"fasel") - utils.AssertEqual(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4 @@ -293,7 +292,7 @@ func Benchmark_Ctx_Attachment(b *testing.B) { // example with quote params c.Attachment("another document.pdf\"\r\nBla: \"fasel") } - utils.AssertEqual(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) } // go test -run Test_Ctx_BaseURL @@ -303,9 +302,9 @@ func Test_Ctx_BaseURL(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") - utils.AssertEqual(t, "http://google.com", c.BaseURL()) + require.Equal(t, "http://google.com", c.BaseURL()) // Check cache - utils.AssertEqual(t, "http://google.com", c.BaseURL()) + require.Equal(t, "http://google.com", c.BaseURL()) } // go test -v -run=^$ -bench=Benchmark_Ctx_BaseURL -benchmem @@ -321,7 +320,7 @@ func Benchmark_Ctx_BaseURL(b *testing.B) { for n := 0; n < b.N; n++ { res = c.BaseURL() } - utils.AssertEqual(b, "http://google.com:1337", res) + require.Equal(b, "http://google.com:1337", res) } // go test -run Test_Ctx_Body @@ -331,7 +330,7 @@ func Test_Ctx_Body(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte("john=doe")) - utils.AssertEqual(t, []byte("john=doe"), c.Body()) + require.Equal(t, []byte("john=doe"), c.Body()) } // go test -run Test_Ctx_Body_With_Compression @@ -344,13 +343,13 @@ func Test_Ctx_Body_With_Compression(t *testing.T) { var b bytes.Buffer gz := gzip.NewWriter(&b) _, err := gz.Write([]byte("john=doe")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) err = gz.Flush() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) err = gz.Close() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) c.Request().SetBody(b.Bytes()) - utils.AssertEqual(t, []byte("john=doe"), c.Body()) + require.Equal(t, []byte("john=doe"), c.Body()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Body_With_Compression -benchmem -count=4 @@ -362,11 +361,11 @@ func Benchmark_Ctx_Body_With_Compression(b *testing.B) { var buf bytes.Buffer gz := gzip.NewWriter(&buf) _, err := gz.Write([]byte("john=doe")) - utils.AssertEqual(b, nil, err) + require.NoError(b, err) err = gz.Flush() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) err = gz.Close() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) c.Request().SetBody(buf.Bytes()) @@ -374,7 +373,7 @@ func Benchmark_Ctx_Body_With_Compression(b *testing.B) { _ = c.Body() } - utils.AssertEqual(b, []byte("john=doe"), c.Body()) + require.Equal(b, []byte("john=doe"), c.Body()) } // go test -run Test_Ctx_Context @@ -383,7 +382,7 @@ func Test_Ctx_Context(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.Context())) + require.Equal(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.Context())) } // go test -run Test_Ctx_UserContext @@ -393,13 +392,13 @@ func Test_Ctx_UserContext(t *testing.T) { t.Run("Nil_Context", func(t *testing.T) { ctx := c.UserContext() - utils.AssertEqual(t, ctx, context.Background()) + require.Equal(t, ctx, context.Background()) }) t.Run("ValueContext", func(t *testing.T) { testKey := struct{}{} testValue := "Test Value" ctx := context.WithValue(context.Background(), testKey, testValue) - utils.AssertEqual(t, testValue, ctx.Value(testKey)) + require.Equal(t, testValue, ctx.Value(testKey)) }) } @@ -412,7 +411,7 @@ func Test_Ctx_SetUserContext(t *testing.T) { testValue := "Test Value" ctx := context.WithValue(context.Background(), testKey, testValue) c.SetUserContext(ctx) - utils.AssertEqual(t, testValue, c.UserContext().Value(testKey)) + require.Equal(t, testValue, c.UserContext().Value(testKey)) } // go test -run Test_Ctx_UserContext_Multiple_Requests @@ -440,12 +439,12 @@ func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) { t.Run(fmt.Sprintf("request_%d", i), func(t *testing.T) { resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil)) - utils.AssertEqual(t, nil, err, "Unexpected error from response") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "context.Context returned from c.UserContext() is reused") + require.NoError(t, err, "Unexpected error from response") + require.Equal(t, StatusOK, resp.StatusCode, "context.Context returned from c.UserContext() is reused") b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "Unexpected error from reading response body") - utils.AssertEqual(t, fmt.Sprintf("resp_%d_returned", i), string(b), "response text incorrect") + require.NoError(t, err, "Unexpected error from reading response body") + require.Equal(t, fmt.Sprintf("resp_%d_returned", i), string(b), "response text incorrect") }) } } @@ -468,23 +467,23 @@ func Test_Ctx_Cookie(t *testing.T) { } c.Cookie(cookie) expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax" - utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) expect = "username=john; expires=" + httpdate + "; path=/" cookie.SameSite = CookieSameSiteDisabled c.Cookie(cookie) - utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict" cookie.SameSite = CookieSameSiteStrictMode c.Cookie(cookie) - utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None" cookie.Secure = true cookie.SameSite = CookieSameSiteNoneMode c.Cookie(cookie) - utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) expect = "username=john; path=/; secure; SameSite=None" // should remove expires and max-age headers @@ -492,7 +491,7 @@ func Test_Ctx_Cookie(t *testing.T) { cookie.Expires = expire cookie.MaxAge = 10000 c.Cookie(cookie) - utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4 @@ -508,7 +507,7 @@ func Benchmark_Ctx_Cookie(b *testing.B) { Value: "Doe", }) } - utils.AssertEqual(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie"))) + require.Equal(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie"))) } // go test -run Test_Ctx_Cookies @@ -518,8 +517,8 @@ func Test_Ctx_Cookies(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set("Cookie", "john=doe") - utils.AssertEqual(t, "doe", c.Cookies("john")) - utils.AssertEqual(t, "default", c.Cookies("unknown", "default")) + require.Equal(t, "doe", c.Cookies("john")) + require.Equal(t, "default", c.Cookies("unknown", "default")) } // go test -run Test_Ctx_Format @@ -530,35 +529,35 @@ func Test_Ctx_Format(t *testing.T) { c.Request().Header.Set(HeaderAccept, MIMETextPlain) c.Format([]byte("Hello, World!")) - utils.AssertEqual(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Response().Body())) c.Request().Header.Set(HeaderAccept, MIMETextHTML) c.Format("Hello, World!") - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) c.Request().Header.Set(HeaderAccept, MIMEApplicationJSON) c.Format("Hello, World!") - utils.AssertEqual(t, `"Hello, World!"`, string(c.Response().Body())) + require.Equal(t, `"Hello, World!"`, string(c.Response().Body())) c.Request().Header.Set(HeaderAccept, MIMETextPlain) c.Format(complex(1, 1)) - utils.AssertEqual(t, "(1+1i)", string(c.Response().Body())) + require.Equal(t, "(1+1i)", string(c.Response().Body())) c.Request().Header.Set(HeaderAccept, MIMEApplicationXML) c.Format("Hello, World!") - utils.AssertEqual(t, `Hello, World!`, string(c.Response().Body())) + require.Equal(t, `Hello, World!`, string(c.Response().Body())) err := c.Format(complex(1, 1)) - utils.AssertEqual(t, true, err != nil) + require.True(t, err != nil) c.Request().Header.Set(HeaderAccept, MIMETextPlain) c.Format(Map{}) - utils.AssertEqual(t, "map[]", string(c.Response().Body())) + require.Equal(t, "map[]", string(c.Response().Body())) type broken string c.Request().Header.Set(HeaderAccept, "broken/accept") c.Format(broken("Hello, World!")) - utils.AssertEqual(t, `Hello, World!`, string(c.Response().Body())) + require.Equal(t, `Hello, World!`, string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Format -benchmem -count=4 @@ -569,10 +568,13 @@ func Benchmark_Ctx_Format(b *testing.B) { c.Request().Header.Set("Accept", "text/plain") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Format("Hello, World!") + err = c.Format("Hello, World!") } - utils.AssertEqual(b, `Hello, World!`, string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, `Hello, World!`, string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Format_HTML -benchmem -count=4 @@ -583,10 +585,13 @@ func Benchmark_Ctx_Format_HTML(b *testing.B) { c.Request().Header.Set("Accept", "text/html") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Format("Hello, World!") + err = c.Format("Hello, World!") } - utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, "

Hello, World!

", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Format_JSON -benchmem -count=4 @@ -597,10 +602,13 @@ func Benchmark_Ctx_Format_JSON(b *testing.B) { c.Request().Header.Set("Accept", "application/json") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Format("Hello, World!") + err = c.Format("Hello, World!") } - utils.AssertEqual(b, `"Hello, World!"`, string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, `"Hello, World!"`, string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Format_XML -benchmem -count=4 @@ -611,10 +619,13 @@ func Benchmark_Ctx_Format_XML(b *testing.B) { c.Request().Header.Set("Accept", "application/xml") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Format("Hello, World!") + err = c.Format("Hello, World!") } - utils.AssertEqual(b, `Hello, World!`, string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, `Hello, World!`, string(c.Response().Body())) } // go test -run Test_Ctx_FormFile @@ -625,18 +636,18 @@ func Test_Ctx_FormFile(t *testing.T) { app.Post("/test", func(c Ctx) error { fh, err := c.FormFile("file") - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "test", fh.Filename) + require.NoError(t, err) + require.Equal(t, "test", fh.Filename) f, err := fh.Open() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) b := new(bytes.Buffer) _, err = io.Copy(b, f) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) f.Close() - utils.AssertEqual(t, "hello world", b.String()) + require.Equal(t, "hello world", b.String()) return nil }) @@ -644,10 +655,10 @@ func Test_Ctx_FormFile(t *testing.T) { writer := multipart.NewWriter(body) ioWriter, err := writer.CreateFormFile("file", "test") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) _, err = ioWriter.Write([]byte("hello world")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) writer.Close() @@ -656,8 +667,8 @@ func Test_Ctx_FormFile(t *testing.T) { req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -run Test_Ctx_FormValue @@ -666,14 +677,14 @@ func Test_Ctx_FormValue(t *testing.T) { app := New() app.Post("/test", func(c Ctx) error { - utils.AssertEqual(t, "john", c.FormValue("name")) + require.Equal(t, "john", c.FormValue("name")) return nil }) body := &bytes.Buffer{} writer := multipart.NewWriter(body) - utils.AssertEqual(t, nil, writer.WriteField("name", "john")) + require.Nil(t, writer.WriteField("name", "john")) writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) @@ -681,8 +692,8 @@ func Test_Ctx_FormValue(t *testing.T) { req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -v -run=^$ -bench=Benchmark_Ctx_Fresh_StaleEtag -benchmem -count=4 @@ -707,44 +718,44 @@ func Test_Ctx_Fresh(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "*") c.Request().Header.Set(HeaderCacheControl, "no-cache") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "*") c.Request().Header.Set(HeaderCacheControl, ",no-cache,") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "*") c.Request().Header.Set(HeaderCacheControl, "aa,no-cache,") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "*") c.Request().Header.Set(HeaderCacheControl, ",no-cache,bb") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "675af34563dc-tr34") c.Request().Header.Set(HeaderCacheControl, "public") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfNoneMatch, "a, b") c.Response().Header.Set(HeaderETag, "c") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Response().Header.Set(HeaderETag, "a") - utils.AssertEqual(t, true, c.Fresh()) + require.True(t, c.Fresh()) c.Request().Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT") c.Response().Header.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Response().Header.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) c.Request().Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT") - utils.AssertEqual(t, false, c.Fresh()) + require.False(t, c.Fresh()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Fresh_WithNoCache -benchmem -count=4 @@ -767,23 +778,23 @@ func Test_Ctx_Get(t *testing.T) { c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") c.Request().Header.Set(HeaderReferer, "Monster") - utils.AssertEqual(t, "utf-8, iso-8859-1;q=0.5", c.Get(HeaderAcceptCharset)) - utils.AssertEqual(t, "Monster", c.Get(HeaderReferer)) - utils.AssertEqual(t, "default", c.Get("unknown", "default")) + require.Equal(t, "utf-8, iso-8859-1;q=0.5", c.Get(HeaderAcceptCharset)) + require.Equal(t, "Monster", c.Get(HeaderReferer)) + require.Equal(t, "default", c.Get("unknown", "default")) } -// go test -run Test_Ctx_Hostname -func Test_Ctx_Hostname(t *testing.T) { +// go test -run Test_Ctx_Host +func Test_Ctx_Host(t *testing.T) { t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") - utils.AssertEqual(t, "google.com", c.Hostname()) + require.Equal(t, "google.com", c.Host()) } -// go test -run Test_Ctx_Hostname_Untrusted -func Test_Ctx_Hostname_UntrustedProxy(t *testing.T) { +// go test -run Test_Ctx_Host_UntrustedProxy +func Test_Ctx_Host_UntrustedProxy(t *testing.T) { t.Parallel() // Don't trust any proxy { @@ -791,7 +802,7 @@ func Test_Ctx_Hostname_UntrustedProxy(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") - utils.AssertEqual(t, "google.com", c.Hostname()) + require.Equal(t, "google.com", c.Host()) app.ReleaseCtx(c) } // Trust to specific proxy list @@ -800,7 +811,93 @@ func Test_Ctx_Hostname_UntrustedProxy(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") - utils.AssertEqual(t, "google.com", c.Hostname()) + require.Equal(t, "google.com", c.Host()) + app.ReleaseCtx(c) + } +} + +// go test -run Test_Ctx_Host_TrustedProxy +func Test_Ctx_Host_TrustedProxy(t *testing.T) { + t.Parallel() + { + app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + require.Equal(t, "google1.com", c.Host()) + app.ReleaseCtx(c) + } +} + +// go test -run Test_Ctx_Host_TrustedProxyRange +func Test_Ctx_Host_TrustedProxyRange(t *testing.T) { + t.Parallel() + + app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + require.Equal(t, "google1.com", c.Host()) + app.ReleaseCtx(c) +} + +// go test -run Test_Ctx_Host_UntrustedProxyRange +func Test_Ctx_Host_UntrustedProxyRange(t *testing.T) { + t.Parallel() + + app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + require.Equal(t, "google.com", c.Host()) + app.ReleaseCtx(c) +} + +// go test -v -run=^$ -bench=Benchmark_Ctx_Host -benchmem -count=4 +func Benchmark_Ctx_Host(b *testing.B) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + var host string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + host = c.Host() + } + require.Equal(b, "google.com", host) +} + +// go test -run Test_Ctx_Hostname +func Test_Ctx_Hostname(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Request().SetRequestURI("http://google.com/test") + require.Equal(t, "google.com", c.Hostname()) + + c.Request().SetRequestURI("http://google.com:8080/test") + require.Equal(t, "google.com", c.Hostname()) +} + +// go test -v -run=^$ -bench=Benchmark_Ctx_Hostname -benchmem -count=4 +func Benchmark_Ctx_Hostname(b *testing.B) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com:8080/test") + var hostname string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + hostname = c.Hostname() + } + // Trust to specific proxy list + { + app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + require.Equal(b, "google.com", hostname) app.ReleaseCtx(c) } } @@ -813,7 +910,20 @@ func Test_Ctx_Hostname_TrustedProxy(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") - utils.AssertEqual(t, "google1.com", c.Hostname()) + require.Equal(t, "google1.com", c.Hostname()) + app.ReleaseCtx(c) + } +} + +// go test -run Test_Ctx_Hostname_Trusted_Multiple +func Test_Ctx_Hostname_TrustedProxy_Multiple(t *testing.T) { + t.Parallel() + { + app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com, google2.com") + require.Equal(t, "google1.com", c.Hostname()) app.ReleaseCtx(c) } } @@ -826,7 +936,7 @@ func Test_Ctx_Hostname_TrustedProxyRange(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") - utils.AssertEqual(t, "google1.com", c.Hostname()) + require.Equal(t, "google1.com", c.Hostname()) app.ReleaseCtx(c) } @@ -838,7 +948,7 @@ func Test_Ctx_Hostname_UntrustedProxyRange(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") - utils.AssertEqual(t, "google.com", c.Hostname()) + require.Equal(t, "google.com", c.Hostname()) app.ReleaseCtx(c) } @@ -848,7 +958,7 @@ func Test_Ctx_Port(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "0", c.Port()) + require.Equal(t, "0", c.Port()) } // go test -run Test_Ctx_PortInHandler @@ -861,30 +971,92 @@ func Test_Ctx_PortInHandler(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/port", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "0", string(body)) + require.NoError(t, err) + require.Equal(t, "0", string(body)) } // go test -run Test_Ctx_IP func Test_Ctx_IP(t *testing.T) { t.Parallel() + app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "0.0.0.0", c.IP()) + // default behaviour will return the remote IP from the stack + require.Equal(t, "0.0.0.0", c.IP()) + + // X-Forwarded-For is set, but it is ignored because proxyHeader is not set + c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") + require.Equal(t, "0.0.0.0", c.IP()) } // go test -run Test_Ctx_IP_ProxyHeader func Test_Ctx_IP_ProxyHeader(t *testing.T) { t.Parallel() - app := New(Config{ProxyHeader: "Real-Ip"}) - c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "", c.IP()) + // make sure that the same behaviour exists for different proxy header names + proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor} + + for _, proxyHeaderName := range proxyHeaderNames { + app := New(Config{ProxyHeader: proxyHeaderName}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Request().Header.Set(proxyHeaderName, "0.0.0.1") + require.Equal(t, "0.0.0.1", c.IP()) + + // without IP validation we return the full string + c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") + require.Equal(t, "0.0.0.1, 0.0.0.2", c.IP()) + + // without IP validation we return invalid IPs + c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") + require.Equal(t, "invalid, 0.0.0.2, 0.0.0.3", c.IP()) + + // when proxy header is enabled but the value is empty, without IP validation we return an empty string + c.Request().Header.Set(proxyHeaderName, "") + require.Equal(t, "", c.IP()) + + // without IP validation we return an invalid IP + c.Request().Header.Set(proxyHeaderName, "not-valid-ip") + require.Equal(t, "not-valid-ip", c.IP()) + } +} + +// go test -run Test_Ctx_IP_ProxyHeader +func Test_Ctx_IP_ProxyHeader_With_IP_Validation(t *testing.T) { + t.Parallel() + + // make sure that the same behaviour exists for different proxy header names + proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor} + + for _, proxyHeaderName := range proxyHeaderNames { + app := New(Config{EnableIPValidation: true, ProxyHeader: proxyHeaderName}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + + // when proxy header & validation is enabled and the value is a valid IP, we return it + c.Request().Header.Set(proxyHeaderName, "0.0.0.1") + require.Equal(t, "0.0.0.1", c.IP()) + + // when proxy header & validation is enabled and the value is a list of IPs, we return the first valid IP + c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") + require.Equal(t, "0.0.0.1", c.IP()) + + c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") + require.Equal(t, "0.0.0.2", c.IP()) + + // when proxy header & validation is enabled but the value is empty, we will ignore the header + c.Request().Header.Set(proxyHeaderName, "") + require.Equal(t, "0.0.0.0", c.IP()) + + // when proxy header & validation is enabled but the value is not an IP, we will ignore the header + // and return the IP of the caller + c.Request().Header.Set(proxyHeaderName, "not-valid-ip") + require.Equal(t, "0.0.0.0", c.IP()) + } } // go test -run Test_Ctx_IP_UntrustedProxy @@ -893,8 +1065,7 @@ func Test_Ctx_IP_UntrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}, ProxyHeader: HeaderXForwardedFor}) c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") - - utils.AssertEqual(t, "0.0.0.0", c.IP()) + require.Equal(t, "0.0.0.0", c.IP()) } // go test -run Test_Ctx_IP_TrustedProxy @@ -903,8 +1074,7 @@ func Test_Ctx_IP_TrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: HeaderXForwardedFor}) c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") - - utils.AssertEqual(t, "0.0.0.1", c.IP()) + require.Equal(t, "0.0.0.1", c.IP()) } // go test -run Test_Ctx_IPs -parallel @@ -913,29 +1083,165 @@ func Test_Ctx_IPs(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) + // normal happy path test case c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") - utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + // inconsistent space formatting c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") - utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + // invalid IPs are allowed to be returned + c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") + require.Equal(t, []string{"invalid", "127.0.0.1", "127.0.0.2"}, c.IPs()) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") + require.Equal(t, []string{"127.0.0.1", "invalid", "127.0.0.2"}, c.IPs()) + + // ensure that the ordering of IPs in the header is maintained + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") + require.Equal(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs()) + + // ensure for IPv6 + c.Request().Header.Set(HeaderXForwardedFor, "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d, invalid, 2345:0425:2CA1::0567:5673:23b5") + require.Equal(t, []string{"9396:9549:b4f7:8ed0:4791:1330:8c06:e62d", "invalid", "2345:0425:2CA1::0567:5673:23b5"}, c.IPs()) + + // empty header c.Request().Header.Set(HeaderXForwardedFor, "") - utils.AssertEqual(t, 0, len(c.IPs())) + require.Equal(t, 0, len(c.IPs())) + + // missing header + c.Request() + require.Equal(t, 0, len(c.IPs())) +} + +func Test_Ctx_IPs_With_IP_Validation(t *testing.T) { + t.Parallel() + app := New(Config{EnableIPValidation: true}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + + // normal happy path test case + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") + require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + + // inconsistent space formatting + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") + require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) + + // invalid IPs are in the header + c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") + require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs()) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") + require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs()) + + // ensure that the ordering of IPs in the header is maintained + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") + require.Equal(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs()) + + // ensure for IPv6 + c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 9396:9549:b4f7:8ed0:4791:1330:8c06:e62d") + require.Equal(t, []string{"f037:825e:eadb:1b7b:1667:6f0a:5356:f604", "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"}, c.IPs()) + + // empty header + c.Request().Header.Set(HeaderXForwardedFor, "") + require.Equal(t, 0, len(c.IPs())) + + // missing header + c.Request() + require.Equal(t, 0, len(c.IPs())) } // go test -v -run=^$ -bench=Benchmark_Ctx_IPs -benchmem -count=4 func Benchmark_Ctx_IPs(b *testing.B) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") + var res []string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IPs() + } + require.Equal(b, []string{"127.0.0.1", "invalid", "127.0.0.1"}, res) +} + +func Benchmark_Ctx_IPs_v6(b *testing.B) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 2345:0425:2CA1::0567:5673:23b5") + var res []string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IPs() + } + require.Equal(b, []string{"f037:825e:eadb:1b7b:1667:6f0a:5356:f604", "invalid", "2345:0425:2CA1::0567:5673:23b5"}, res) +} + +func Benchmark_Ctx_IPs_With_IP_Validation(b *testing.B) { + app := New(Config{EnableIPValidation: true}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") + var res []string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IPs() + } + require.Equal(b, []string{"127.0.0.1", "127.0.0.1"}, res) +} - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.1, 127.0.0.1") +func Benchmark_Ctx_IPs_v6_With_IP_Validation(b *testing.B) { + app := New(Config{EnableIPValidation: true}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + c.Request().Header.Set(HeaderXForwardedFor, "2345:0425:2CA1:0000:0000:0567:5673:23b5, invalid, 2345:0425:2CA1::0567:5673:23b5") var res []string b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { res = c.IPs() } - utils.AssertEqual(b, []string{"127.0.0.1", "127.0.0.1", "127.0.0.1"}, res) + require.Equal(b, []string{"2345:0425:2CA1:0000:0000:0567:5673:23b5", "2345:0425:2CA1::0567:5673:23b5"}, res) +} + +func Benchmark_Ctx_IP_With_ProxyHeader(b *testing.B) { + app := New(Config{ProxyHeader: HeaderXForwardedFor}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + var res string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IP() + } + require.Equal(b, "127.0.0.1", res) +} + +func Benchmark_Ctx_IP_With_ProxyHeader_and_IP_Validation(b *testing.B) { + app := New(Config{ProxyHeader: HeaderXForwardedFor, EnableIPValidation: true}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + var res string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IP() + } + require.Equal(b, "127.0.0.1", res) +} + +func Benchmark_Ctx_IP(b *testing.B) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + c.Request() + var res string + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + res = c.IP() + } + require.Equal(b, "0.0.0.0", res) } // go test -run Test_Ctx_Is @@ -945,32 +1251,32 @@ func Test_Ctx_Is(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderContentType, MIMETextHTML+"; boundary=something") - utils.AssertEqual(t, true, c.Is(".html")) - utils.AssertEqual(t, true, c.Is("html")) - utils.AssertEqual(t, false, c.Is("json")) - utils.AssertEqual(t, false, c.Is(".json")) - utils.AssertEqual(t, false, c.Is("")) - utils.AssertEqual(t, false, c.Is(".foooo")) + require.True(t, c.Is(".html")) + require.True(t, c.Is("html")) + require.False(t, c.Is("json")) + require.False(t, c.Is(".json")) + require.False(t, c.Is("")) + require.False(t, c.Is(".foooo")) c.Request().Header.Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8) - utils.AssertEqual(t, false, c.Is("html")) - utils.AssertEqual(t, true, c.Is("json")) - utils.AssertEqual(t, true, c.Is(".json")) + require.False(t, c.Is("html")) + require.True(t, c.Is("json")) + require.True(t, c.Is(".json")) c.Request().Header.Set(HeaderContentType, " application/json;charset=UTF-8") - utils.AssertEqual(t, false, c.Is("html")) - utils.AssertEqual(t, true, c.Is("json")) - utils.AssertEqual(t, true, c.Is(".json")) + require.False(t, c.Is("html")) + require.True(t, c.Is("json")) + require.True(t, c.Is(".json")) c.Request().Header.Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) - utils.AssertEqual(t, false, c.Is("html")) - utils.AssertEqual(t, true, c.Is("xml")) - utils.AssertEqual(t, true, c.Is(".xml")) + require.False(t, c.Is("html")) + require.True(t, c.Is("xml")) + require.True(t, c.Is(".xml")) c.Request().Header.Set(HeaderContentType, MIMETextPlain) - utils.AssertEqual(t, false, c.Is("html")) - utils.AssertEqual(t, true, c.Is("txt")) - utils.AssertEqual(t, true, c.Is(".txt")) + require.False(t, c.Is("html")) + require.True(t, c.Is("txt")) + require.True(t, c.Is(".txt")) } // go test -v -run=^$ -bench=Benchmark_Ctx_Is -benchmem -count=4 @@ -986,7 +1292,7 @@ func Benchmark_Ctx_Is(b *testing.B) { _ = c.Is(".json") res = c.Is("json") } - utils.AssertEqual(b, true, res) + require.True(b, res) } // go test -run Test_Ctx_Locals @@ -997,12 +1303,12 @@ func Test_Ctx_Locals(t *testing.T) { return c.Next() }) app.Get("/test", func(c Ctx) error { - utils.AssertEqual(t, "doe", c.Locals("john")) + require.Equal(t, "doe", c.Locals("john")) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -run Test_Ctx_Method @@ -1013,12 +1319,73 @@ func Test_Ctx_Method(t *testing.T) { app := New() c := app.NewCtx(fctx) - utils.AssertEqual(t, MethodGet, c.Method()) + require.Equal(t, MethodGet, c.Method()) c.Method(MethodPost) - utils.AssertEqual(t, MethodPost, c.Method()) + require.Equal(t, MethodPost, c.Method()) c.Method("MethodInvalid") - utils.AssertEqual(t, MethodPost, c.Method()) + require.Equal(t, MethodPost, c.Method()) +} + +// go test -run Test_Ctx_ClientHelloInfo +func Test_Ctx_ClientHelloInfo(t *testing.T) { + t.Parallel() + app := New() + app.Get("/ServerName", func(c Ctx) error { + result := c.ClientHelloInfo() + if result != nil { + return c.SendString(result.ServerName) + } + + return c.SendString("ClientHelloInfo is nil") + }) + app.Get("/SignatureSchemes", func(c Ctx) error { + result := c.ClientHelloInfo() + if result != nil { + return c.JSON(result.SignatureSchemes) + } + + return c.SendString("ClientHelloInfo is nil") + }) + app.Get("/SupportedVersions", func(c Ctx) error { + result := c.ClientHelloInfo() + if result != nil { + return c.JSON(result.SupportedVersions) + } + + return c.SendString("ClientHelloInfo is nil") + }) + + // Test without TLS handler + resp, _ := app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) + body, _ := io.ReadAll(resp.Body) + require.Equal(t, []byte("ClientHelloInfo is nil"), body) + + // Test with TLS Handler + const ( + PSSWithSHA256 = 0x0804 + VersionTLS13 = 0x0304 + ) + app.tlsHandler = &TLSHandler{clientHelloInfo: &tls.ClientHelloInfo{ + ServerName: "example.golang", + SignatureSchemes: []tls.SignatureScheme{PSSWithSHA256}, + SupportedVersions: []uint16{VersionTLS13}, + }} + + // Test ServerName + resp, _ = app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) + body, _ = io.ReadAll(resp.Body) + require.Equal(t, []byte("example.golang"), body) + + // Test SignatureSchemes + resp, _ = app.Test(httptest.NewRequest(MethodGet, "/SignatureSchemes", nil)) + body, _ = io.ReadAll(resp.Body) + require.Equal(t, "["+strconv.Itoa(PSSWithSHA256)+"]", string(body)) + + // Test SupportedVersions + resp, _ = app.Test(httptest.NewRequest(MethodGet, "/SupportedVersions", nil)) + body, _ = io.ReadAll(resp.Body) + require.Equal(t, "["+strconv.Itoa(VersionTLS13)+"]", string(body)) } // go test -run Test_Ctx_InvalidMethod @@ -1035,8 +1402,8 @@ func Test_Ctx_InvalidMethod(t *testing.T) { app.Handler()(fctx) - utils.AssertEqual(t, 400, fctx.Response.StatusCode()) - utils.AssertEqual(t, []byte("Invalid http method"), fctx.Response.Body()) + require.Equal(t, 400, fctx.Response.StatusCode()) + require.Equal(t, []byte("Invalid http method"), fctx.Response.Body()) } // go test -run Test_Ctx_MultipartForm @@ -1046,15 +1413,15 @@ func Test_Ctx_MultipartForm(t *testing.T) { app.Post("/test", func(c Ctx) error { result, err := c.MultipartForm() - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "john", result.Value["name"][0]) + require.NoError(t, err) + require.Equal(t, "john", result.Value["name"][0]) return nil }) body := &bytes.Buffer{} writer := multipart.NewWriter(body) - utils.AssertEqual(t, nil, writer.WriteField("name", "john")) + require.Nil(t, writer.WriteField("name", "john")) writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) @@ -1062,8 +1429,8 @@ func Test_Ctx_MultipartForm(t *testing.T) { req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -v -run=^$ -bench=Benchmark_Ctx_MultipartForm -benchmem -count=4 @@ -1099,7 +1466,7 @@ func Test_Ctx_OriginalURL(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetRequestURI("http://google.com/test?search=demo") - utils.AssertEqual(t, "http://google.com/test?search=demo", c.OriginalURL()) + require.Equal(t, "http://google.com/test?search=demo", c.OriginalURL()) } // go test -race -run Test_Ctx_Params @@ -1107,38 +1474,69 @@ func Test_Ctx_Params(t *testing.T) { t.Parallel() app := New() app.Get("/test/:user", func(c Ctx) error { - utils.AssertEqual(t, "john", c.Params("user")) + require.Equal(t, "john", c.Params("user")) return nil }) app.Get("/test2/*", func(c Ctx) error { - utils.AssertEqual(t, "im/a/cookie", c.Params("*")) + require.Equal(t, "im/a/cookie", c.Params("*")) return nil }) app.Get("/test3/*/blafasel/*", func(c Ctx) error { - utils.AssertEqual(t, "1111", c.Params("*1")) - utils.AssertEqual(t, "2222", c.Params("*2")) - utils.AssertEqual(t, "1111", c.Params("*")) + require.Equal(t, "1111", c.Params("*1")) + require.Equal(t, "2222", c.Params("*2")) + require.Equal(t, "1111", c.Params("*")) return nil }) app.Get("/test4/:optional?", func(c Ctx) error { - utils.AssertEqual(t, "", c.Params("optional")) + require.Equal(t, "", c.Params("optional")) + return nil + }) + app.Get("/test5/:id/:Id", func(c Ctx) error { + require.Equal(t, "first", c.Params("id")) + require.Equal(t, "first", c.Params("Id")) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/john", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/test2/im/a/cookie", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/test3/1111/blafasel/2222", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/test4", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test5/first/second", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") +} + +func Test_Ctx_Params_Case_Sensitive(t *testing.T) { + t.Parallel() + app := New(Config{CaseSensitive: true}) + app.Get("/test/:User", func(c Ctx) error { + require.Equal(t, "john", c.Params("User")) + require.Equal(t, "", c.Params("user")) + return nil + }) + app.Get("/test2/:id/:Id", func(c Ctx) error { + require.Equal(t, "first", c.Params("id")) + require.Equal(t, "second", c.Params("Id")) + return nil + }) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/john", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test2/first/second", nil)) + require.Equal(t, nil, err) + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -v -run=^$ -bench=Benchmark_Ctx_Params -benchmem -count=4 @@ -1163,7 +1561,7 @@ func Benchmark_Ctx_Params(b *testing.B) { _ = c.Params("param3") res = c.Params("param4") } - utils.AssertEqual(b, "awesome", res) + require.Equal(b, "awesome", res) } // go test -run Test_Ctx_Path @@ -1171,23 +1569,23 @@ func Test_Ctx_Path(t *testing.T) { t.Parallel() app := New(Config{UnescapePath: true}) app.Get("/test/:user", func(c Ctx) error { - utils.AssertEqual(t, "/Test/John", c.Path()) + require.Equal(t, "/Test/John", c.Path()) // not strict && case insensitive - utils.AssertEqual(t, "/ABC/", c.Path("/ABC/")) - utils.AssertEqual(t, "/test/john/", c.Path("/test/john/")) + require.Equal(t, "/ABC/", c.Path("/ABC/")) + require.Equal(t, "/test/john/", c.Path("/test/john/")) return nil }) // test with special chars app.Get("/specialChars/:name", func(c Ctx) error { - utils.AssertEqual(t, "/specialChars/créer", c.Path()) + require.Equal(t, "/specialChars/créer", c.Path()) // unescape is also working if you set the path afterwards - utils.AssertEqual(t, "/اختبار/", c.Path("/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1/")) + require.Equal(t, "/اختبار/", c.Path("/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1/")) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/specialChars/cr%C3%A9er", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -run Test_Ctx_Protocol @@ -1196,10 +1594,10 @@ func Test_Ctx_Protocol(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "HTTP/1.1", c.Protocol()) + require.Equal(t, "HTTP/1.1", c.Protocol()) c.Request().Header.SetProtocol("HTTP/2") - utils.AssertEqual(t, "HTTP/2", c.Protocol()) + require.Equal(t, "HTTP/2", c.Protocol()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Protocol -benchmem -count=4 @@ -1215,7 +1613,7 @@ func Benchmark_Ctx_Protocol(b *testing.B) { res = c.Protocol() } - utils.AssertEqual(b, "HTTP/1.1", res) + require.Equal(b, "HTTP/1.1", res) } // go test -run Test_Ctx_Scheme @@ -1228,22 +1626,22 @@ func Test_Ctx_Scheme(t *testing.T) { c := app.NewCtx(freq) c.Request().Header.Set(HeaderXForwardedProto, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedProtocol, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXUrlScheme, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Scheme -benchmem -count=4 @@ -1257,7 +1655,7 @@ func Benchmark_Ctx_Scheme(b *testing.B) { for n := 0; n < b.N; n++ { res = c.Scheme() } - utils.AssertEqual(b, "http", res) + require.Equal(b, "http", res) } // go test -run Test_Ctx_Scheme_TrustedProxy @@ -1267,22 +1665,22 @@ func Test_Ctx_Scheme_TrustedProxy(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedProtocol, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXUrlScheme, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) } // go test -run Test_Ctx_Scheme_TrustedProxyRange @@ -1292,22 +1690,22 @@ func Test_Ctx_Scheme_TrustedProxyRange(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedProtocol, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXUrlScheme, "https") - utils.AssertEqual(t, "https", c.Scheme()) + require.Equal(t, "https", c.Scheme()) c.Request().Header.Reset() - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) } // go test -run Test_Ctx_Scheme_UntrustedProxyRange @@ -1317,22 +1715,22 @@ func Test_Ctx_Scheme_UntrustedProxyRange(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedProtocol, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXUrlScheme, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) } // go test -run Test_Ctx_Scheme_UnTrustedProxy @@ -1342,22 +1740,22 @@ func Test_Ctx_Scheme_UnTrustedProxy(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedProtocol, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXUrlScheme, "https") - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) c.Request().Header.Reset() - utils.AssertEqual(t, "http", c.Scheme()) + require.Equal(t, "http", c.Scheme()) } // go test -run Test_Ctx_Query @@ -1367,9 +1765,9 @@ func Test_Ctx_Query(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().URI().SetQueryString("search=john&age=20") - utils.AssertEqual(t, "john", c.Query("search")) - utils.AssertEqual(t, "20", c.Query("age")) - utils.AssertEqual(t, "default", c.Query("unknown", "default")) + require.Equal(t, "john", c.Query("search")) + require.Equal(t, "20", c.Query("age")) + require.Equal(t, "default", c.Query("unknown", "default")) } // go test -run Test_Ctx_Range @@ -1384,27 +1782,27 @@ func Test_Ctx_Range(t *testing.T) { ) _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + require.True(t, err != nil) c.Request().Header.Set(HeaderRange, "bytes=500") _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + require.True(t, err != nil) c.Request().Header.Set(HeaderRange, "bytes=500=") _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + require.True(t, err != nil) c.Request().Header.Set(HeaderRange, "bytes=500-300") _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + require.True(t, err != nil) testRange := func(header string, start, end int) { c.Request().Header.Set(HeaderRange, header) result, err = c.Range(1000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "bytes", result.Type) - utils.AssertEqual(t, start, result.Ranges[0].Start) - utils.AssertEqual(t, end, result.Ranges[0].End) + require.NoError(t, err) + require.Equal(t, "bytes", result.Type) + require.Equal(t, start, result.Ranges[0].Start) + require.Equal(t, end, result.Ranges[0].End) } testRange("bytes=a-700", 300, 999) @@ -1418,18 +1816,18 @@ func Test_Ctx_Route(t *testing.T) { t.Parallel() app := New() app.Get("/test", func(c Ctx) error { - utils.AssertEqual(t, "/test", c.Route().Path) + require.Equal(t, "/test", c.Route().Path) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "/", c.Route().Path) - utils.AssertEqual(t, MethodGet, c.Route().Method) - utils.AssertEqual(t, 0, len(c.Route().Handlers)) + require.Equal(t, "/", c.Route().Path) + require.Equal(t, MethodGet, c.Route().Method) + require.Equal(t, 0, len(c.Route().Handlers)) } // go test -run Test_Ctx_RouteNormalized @@ -1437,12 +1835,12 @@ func Test_Ctx_RouteNormalized(t *testing.T) { t.Parallel() app := New() app.Get("/test", func(c Ctx) error { - utils.AssertEqual(t, "/test", c.Route().Path) + require.Equal(t, "/test", c.Route().Path) return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "//test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") } // go test -run Test_Ctx_SaveFile @@ -1453,18 +1851,18 @@ func Test_Ctx_SaveFile(t *testing.T) { app.Post("/test", func(c Ctx) error { fh, err := c.FormFile("file") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) tempFile, err := os.CreateTemp(os.TempDir(), "test-") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer os.Remove(tempFile.Name()) err = c.SaveFile(fh, tempFile.Name()) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) bs, err := os.ReadFile(tempFile.Name()) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hello world", string(bs)) + require.NoError(t, err) + require.Equal(t, "hello world", string(bs)) return nil }) @@ -1472,10 +1870,10 @@ func Test_Ctx_SaveFile(t *testing.T) { writer := multipart.NewWriter(body) ioWriter, err := writer.CreateFormFile("file", "test") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) _, err = ioWriter.Write([]byte("hello world")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) @@ -1483,8 +1881,8 @@ func Test_Ctx_SaveFile(t *testing.T) { req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -run Test_Ctx_SaveFileToStorage @@ -1495,17 +1893,17 @@ func Test_Ctx_SaveFileToStorage(t *testing.T) { app.Post("/test", func(c Ctx) error { fh, err := c.FormFile("file") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) err = c.SaveFileToStorage(fh, "test", storage) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) file, err := storage.Get("test") - utils.AssertEqual(t, []byte("hello world"), file) - utils.AssertEqual(t, nil, err) + require.Equal(t, []byte("hello world"), file) + require.NoError(t, err) err = storage.Delete("test") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) return nil }) @@ -1514,10 +1912,10 @@ func Test_Ctx_SaveFileToStorage(t *testing.T) { writer := multipart.NewWriter(body) ioWriter, err := writer.CreateFormFile("file", "test") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) _, err = ioWriter.Write([]byte("hello world")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) @@ -1525,8 +1923,8 @@ func Test_Ctx_SaveFileToStorage(t *testing.T) { req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } // go test -run Test_Ctx_Secure @@ -1536,7 +1934,7 @@ func Test_Ctx_Secure(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) // TODO Add TLS conn - utils.AssertEqual(t, false, c.Secure()) + require.False(t, c.Secure()) } // go test -run Test_Ctx_Stale @@ -1545,7 +1943,7 @@ func Test_Ctx_Stale(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, true, c.Stale()) + require.True(t, c.Stale()) } // go test -run Test_Ctx_Subdomains @@ -1555,10 +1953,10 @@ func Test_Ctx_Subdomains(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().URI().SetHost("john.doe.is.awesome.google.com") - utils.AssertEqual(t, []string{"john", "doe"}, c.Subdomains(4)) + require.Equal(t, []string{"john", "doe"}, c.Subdomains(4)) c.Request().URI().SetHost("localhost:3000") - utils.AssertEqual(t, []string{"localhost:3000"}, c.Subdomains()) + require.Equal(t, []string{"localhost:3000"}, c.Subdomains()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Subdomains -benchmem -count=4 @@ -1573,7 +1971,7 @@ func Benchmark_Ctx_Subdomains(b *testing.B) { for n := 0; n < b.N; n++ { res = c.Subdomains() } - utils.AssertEqual(b, []string{"john", "doe"}, res) + require.Equal(b, []string{"john", "doe"}, res) } // go test -run Test_Ctx_ClearCookie @@ -1584,13 +1982,13 @@ func Test_Ctx_ClearCookie(t *testing.T) { c.Request().Header.Set(HeaderCookie, "john=doe") c.ClearCookie("john") - utils.AssertEqual(t, true, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires=")) + require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires=")) c.Request().Header.Set(HeaderCookie, "test1=dummy") c.Request().Header.Set(HeaderCookie, "test2=dummy") c.ClearCookie() - utils.AssertEqual(t, true, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test1=; expires=")) - utils.AssertEqual(t, true, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test2=; expires=")) + require.True(t, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test1=; expires=")) + require.True(t, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test2=; expires=")) } // go test -race -run Test_Ctx_Download @@ -1599,19 +1997,19 @@ func Test_Ctx_Download(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Download("ctx.go", "Awesome File!") + require.Equal(t, nil, c.Download("ctx.go", "Awesome File!")) f, err := os.Open("./ctx.go") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer f.Close() expect, err := io.ReadAll(f) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, expect, c.Response().Body()) - utils.AssertEqual(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.NoError(t, err) + require.Equal(t, expect, c.Response().Body()) + require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition))) - c.Download("ctx.go") - utils.AssertEqual(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.NoError(t, c.Download("ctx.go")) + require.Equal(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition))) } // go test -race -run Test_Ctx_SendFile @@ -1621,30 +2019,30 @@ func Test_Ctx_SendFile(t *testing.T) { // fetch file content f, err := os.Open("./ctx.go") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer f.Close() expectFileContent, err := io.ReadAll(f) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // fetch file info for the not modified test case fI, err := os.Stat("./ctx.go") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // simple test case c := app.NewCtx(&fasthttp.RequestCtx{}) err = c.SendFile("ctx.go") // check expectation - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, expectFileContent, c.Response().Body()) - utils.AssertEqual(t, StatusOK, c.Response().StatusCode()) + require.NoError(t, err) + require.Equal(t, expectFileContent, c.Response().Body()) + require.Equal(t, StatusOK, c.Response().StatusCode()) app.ReleaseCtx(c) // test with custom error code c = app.NewCtx(&fasthttp.RequestCtx{}) err = c.Status(StatusInternalServerError).SendFile("ctx.go") // check expectation - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, expectFileContent, c.Response().Body()) - utils.AssertEqual(t, StatusInternalServerError, c.Response().StatusCode()) + require.NoError(t, err) + require.Equal(t, expectFileContent, c.Response().Body()) + require.Equal(t, StatusInternalServerError, c.Response().StatusCode()) app.ReleaseCtx(c) // test not modified @@ -1652,9 +2050,9 @@ func Test_Ctx_SendFile(t *testing.T) { c.Request().Header.Set(HeaderIfModifiedSince, fI.ModTime().Format(time.RFC1123)) err = c.SendFile("ctx.go") // check expectation - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusNotModified, c.Response().StatusCode()) - utils.AssertEqual(t, []byte(nil), c.Response().Body()) + require.NoError(t, err) + require.Equal(t, StatusNotModified, c.Response().StatusCode()) + require.Equal(t, []byte(nil), c.Response().Body()) app.ReleaseCtx(c) } @@ -1664,13 +2062,13 @@ func Test_Ctx_SendFile_404(t *testing.T) { app := New() app.Get("/", func(c Ctx) error { err := c.SendFile(filepath.FromSlash("john_dow.go/")) - utils.AssertEqual(t, false, err == nil) + require.False(t, err == nil) return err }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, StatusNotFound, resp.StatusCode) } // go test -race -run Test_Ctx_SendFile_Immutable @@ -1682,7 +2080,7 @@ func Test_Ctx_SendFile_Immutable(t *testing.T) { endpointsForTest = append(endpointsForTest, endpoint) app.Get(endpoint, func(c Ctx) error { if err := c.SendFile(file); err != nil { - utils.AssertEqual(t, nil, err) + require.NoError(t, err) return err } return c.SendStatus(200) @@ -1697,7 +2095,7 @@ func Test_Ctx_SendFile_Immutable(t *testing.T) { // absolute paths if path, err := filepath.Abs(".github/index.html"); err != nil { - utils.AssertEqual(t, nil, err) + require.NoError(t, err) } else { addEndpoint(path, "/absolute") addEndpoint(filepath.FromSlash(path), "/absoluteOS") // os related @@ -1707,12 +2105,12 @@ func Test_Ctx_SendFile_Immutable(t *testing.T) { t.Run(endpoint, func(t *testing.T) { // 1st try resp, err := app.Test(httptest.NewRequest("GET", endpoint, nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, StatusOK, resp.StatusCode) // 2nd try resp, err = app.Test(httptest.NewRequest("GET", endpoint, nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, StatusOK, resp.StatusCode) }) } } @@ -1724,7 +2122,7 @@ func Test_Ctx_SendFile_RestoreOriginalURL(t *testing.T) { app.Get("/", func(c Ctx) error { originalURL := utils.CopyString(c.OriginalURL()) err := c.SendFile("ctx.go") - utils.AssertEqual(t, originalURL, c.OriginalURL()) + require.Equal(t, originalURL, c.OriginalURL()) return err }) @@ -1732,8 +2130,8 @@ func Test_Ctx_SendFile_RestoreOriginalURL(t *testing.T) { // second request required to confirm with zero allocation _, err2 := app.Test(httptest.NewRequest("GET", "/?test=true", nil)) - utils.AssertEqual(t, nil, err1) - utils.AssertEqual(t, nil, err2) + require.Nil(t, err1) + require.Nil(t, err2) } // go test -run Test_Ctx_JSON @@ -1742,19 +2140,20 @@ func Test_Ctx_JSON(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, true, c.JSON(complex(1, 1)) != nil) + require.True(t, c.JSON(complex(1, 1)) != nil) - c.JSON(Map{ // map has no order + err := c.JSON(Map{ // map has no order "Name": "Grame", "Age": 20, }) - utils.AssertEqual(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body())) - utils.AssertEqual(t, "application/json", string(c.Response().Header.Peek("content-type"))) + require.NoError(t, err) + require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body())) + require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type"))) testEmpty := func(v any, r string) { err := c.JSON(v) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, r, string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, r, string(c.Response().Body())) } testEmpty(nil, "null") @@ -1782,8 +2181,8 @@ func Benchmark_Ctx_JSON(b *testing.B) { for n := 0; n < b.N; n++ { err = c.JSON(data) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) } // go test -run Test_Ctx_JSONP @@ -1792,21 +2191,23 @@ func Test_Ctx_JSONP(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, true, c.JSONP(complex(1, 1)) != nil) + require.True(t, c.JSONP(complex(1, 1)) != nil) - c.JSONP(Map{ + err := c.JSONP(Map{ "Name": "Grame", "Age": 20, }) - utils.AssertEqual(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) - utils.AssertEqual(t, "application/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) + require.NoError(t, err) + require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) + require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) - c.JSONP(Map{ + err = c.JSONP(Map{ "Name": "Grame", "Age": 20, }, "john") - utils.AssertEqual(t, `john({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) - utils.AssertEqual(t, "application/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) + require.NoError(t, err) + require.Equal(t, `john({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) + require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) } // go test -v -run=^$ -bench=Benchmark_Ctx_JSONP -benchmem -count=4 @@ -1829,8 +2230,65 @@ func Benchmark_Ctx_JSONP(b *testing.B) { for n := 0; n < b.N; n++ { err = c.JSONP(data, callback) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body())) +} + +// go test -run Test_Ctx_XML +func Test_Ctx_XML(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + require.True(t, c.JSON(complex(1, 1)) != nil) + + type xmlResult struct { + XMLName xml.Name `xml:"Users"` + Names []string `xml:"Names"` + Ages []int `xml:"Ages"` + } + + err := c.XML(xmlResult{ + Names: []string{"Grame", "John"}, + Ages: []int{1, 12, 20}, + }) + require.NoError(t, err) + require.Equal(t, `GrameJohn11220`, string(c.Response().Body())) + require.Equal(t, "application/xml", string(c.Response().Header.Peek("content-type"))) + + testEmpty := func(v any, r string) { + err := c.XML(v) + require.NoError(t, err) + require.Equal(t, r, string(c.Response().Body())) + } + + testEmpty(nil, "") + testEmpty("", ``) + testEmpty(0, "0") + testEmpty([]int{}, "") +} + +// go test -run=^$ -bench=Benchmark_Ctx_XML -benchmem -count=4 +func Benchmark_Ctx_XML(b *testing.B) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + type SomeStruct struct { + Name string `xml:"Name"` + Age uint8 `xml:"Age"` + } + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + err = c.XML(data) + } + + require.NoError(b, err) + require.Equal(b, `Grame20`, string(c.Response().Body())) } // go test -run Test_Ctx_Links @@ -1840,13 +2298,13 @@ func Test_Ctx_Links(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Links() - utils.AssertEqual(t, "", string(c.Response().Header.Peek(HeaderLink))) + require.Equal(t, "", string(c.Response().Header.Peek(HeaderLink))) c.Links( "http://api.example.com/users?page=2", "next", "http://api.example.com/users?page=5", "last", ) - utils.AssertEqual(t, `; rel="next",; rel="last"`, string(c.Response().Header.Peek(HeaderLink))) + require.Equal(t, `; rel="next",; rel="last"`, string(c.Response().Header.Peek(HeaderLink))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4 @@ -1871,7 +2329,7 @@ func Test_Ctx_Location(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Location("http://example.com") - utils.AssertEqual(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) } // go test -run Test_Ctx_Next @@ -1885,9 +2343,9 @@ func Test_Ctx_Next(t *testing.T) { return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, "Works", resp.Header.Get("X-Next-Result")) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, "Works", resp.Header.Get("X-Next-Result")) } // go test -run Test_Ctx_Next_Error @@ -1899,140 +2357,9 @@ func Test_Ctx_Next_Error(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code") - utils.AssertEqual(t, "Works", resp.Header.Get("X-Next-Result")) -} - -// go test -run Test_Ctx_Redirect -func Test_Ctx_Redirect(t *testing.T) { - t.Parallel() - app := New() - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.Redirect("http://default.com") - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "http://default.com", string(c.Response().Header.Peek(HeaderLocation))) - - c.Redirect("http://example.com", 301) - utils.AssertEqual(t, 301, c.Response().StatusCode()) - utils.AssertEqual(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectToRouteWithParams -func Test_Ctx_RedirectToRouteWithParams(t *testing.T) { - t.Parallel() - app := New() - app.Get("/user/:name", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectToRoute("user", Map{ - "name": "fiber", - }) - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectToRouteWithParams -func Test_Ctx_RedirectToRouteWithQueries(t *testing.T) { - t.Parallel() - app := New() - app.Get("/user/:name", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectToRoute("user", Map{ - "name": "fiber", - "queries": map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"}, - }) - utils.AssertEqual(t, 302, c.Response().StatusCode()) - // analysis of query parameters with url parsing, since a map pass is always randomly ordered - location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) - utils.AssertEqual(t, nil, err, "url.Parse(location)") - utils.AssertEqual(t, "/user/fiber", location.Path) - utils.AssertEqual(t, url.Values{"data[0][name]": []string{"john"}, "data[0][age]": []string{"10"}, "test": []string{"doe"}}, location.Query()) -} - -// go test -run Test_Ctx_RedirectToRouteWithOptionalParams -func Test_Ctx_RedirectToRouteWithOptionalParams(t *testing.T) { - t.Parallel() - app := New() - app.Get("/user/:name?", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectToRoute("user", Map{ - "name": "fiber", - }) - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectToRouteWithOptionalParamsWithoutValue -func Test_Ctx_RedirectToRouteWithOptionalParamsWithoutValue(t *testing.T) { - t.Parallel() - app := New() - app.Get("/user/:name?", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectToRoute("user", Map{}) - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/user/", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectToRouteWithGreedyParameters -func Test_Ctx_RedirectToRouteWithGreedyParameters(t *testing.T) { - t.Parallel() - app := New() - app.Get("/user/+", func(c Ctx) error { - return c.JSON(c.Params("+")) - }).Name("user") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectToRoute("user", Map{ - "+": "test/routes", - }) - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/user/test/routes", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectBack -func Test_Ctx_RedirectBack(t *testing.T) { - t.Parallel() - app := New() - app.Get("/", func(c Ctx) error { - return c.JSON("Home") - }).Name("home") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.RedirectBack("/") - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/", string(c.Response().Header.Peek(HeaderLocation))) -} - -// go test -run Test_Ctx_RedirectBackWithReferer -func Test_Ctx_RedirectBackWithReferer(t *testing.T) { - t.Parallel() - app := New() - app.Get("/", func(c Ctx) error { - return c.JSON("Home") - }).Name("home") - app.Get("/back", func(c Ctx) error { - return c.JSON("Back") - }).Name("back") - c := app.NewCtx(&fasthttp.RequestCtx{}) - - c.Request().Header.Set(HeaderReferer, "/back") - c.RedirectBack("/") - utils.AssertEqual(t, 302, c.Response().StatusCode()) - utils.AssertEqual(t, "/back", c.Get(HeaderReferer)) - utils.AssertEqual(t, "/back", string(c.Response().Header.Peek(HeaderLocation))) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") + require.Equal(t, "Works", resp.Header.Get("X-Next-Result")) } // go test -run Test_Ctx_Render @@ -2049,72 +2376,14 @@ func Test_Ctx_Render(t *testing.T) { _, _ = buf.WriteString("overwrite") defer bytebufferpool.Put(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) err = c.Render("./.github/testdata/template-non-exists.html", nil) - utils.AssertEqual(t, false, err == nil) + require.False(t, err == nil) err = c.Render("./.github/testdata/template-invalid.html", nil) - utils.AssertEqual(t, false, err == nil) -} - -// go test -run Test_Ctx_Render_Mount -func Test_Ctx_Render_Mount(t *testing.T) { - t.Parallel() - - engine := &testTemplateEngine{} - engine.Load() - - sub := New(Config{ - Views: engine, - }) - - sub.Get("/:name", func(c Ctx) error { - return c.Render("hello_world.tmpl", Map{ - "Name": c.Params("name"), - }) - }) - - app := New() - app.Use("/hello", sub) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/a", nil)) - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, nil, err, "app.Test(req)") - - body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello a!

", string(body)) -} - -func Test_Ctx_Render_MountGroup(t *testing.T) { - t.Parallel() - - engine := &testTemplateEngine{} - engine.Load() - - micro := New(Config{ - Views: engine, - }) - - micro.Get("/doe", func(c Ctx) error { - return c.Render("hello_world.tmpl", Map{ - "Name": "doe", - }) - }) - - app := New() - v1 := app.Group("/v1") - v1.Use("/john", micro) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - - body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello doe!

", string(body)) + require.False(t, err == nil) } func Test_Ctx_RenderWithoutLocals(t *testing.T) { @@ -2132,8 +2401,8 @@ func Test_Ctx_RenderWithoutLocals(t *testing.T) { _, _ = buf.WriteString("overwrite") defer bytebufferpool.Put(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

", string(c.Response().Body())) } func Test_Ctx_RenderWithLocals(t *testing.T) { @@ -2151,8 +2420,8 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { _, _ = buf.WriteString("overwrite") defer bytebufferpool.Put(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } @@ -2167,13 +2436,13 @@ func Test_Ctx_RenderWithBindVars(t *testing.T) { }) err := c.Render("./.github/testdata/index.tmpl", Map{}) - + require.NoError(t, err) buf := bytebufferpool.Get() _, _ = buf.WriteString("overwrite") defer bytebufferpool.Put(buf) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } @@ -2186,16 +2455,16 @@ func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) - c.BindVars(Map{ + err := c.BindVars(Map{ "Title": "Hello, World!", }) + require.NoError(t, err) c.Locals("Summary", "Test") - err := c.Render("./.github/testdata/template.tmpl", Map{}) - - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World! Test

", string(c.Response().Body())) + err = c.Render("./.github/testdata/template.tmpl", Map{}) + require.NoError(t, err) + require.Equal(t, "

Hello, World! Test

", string(c.Response().Body())) } @@ -2203,7 +2472,7 @@ func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { t.Parallel() engine := &testTemplateEngine{} err := engine.Load() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) app := New(Config{ PassLocalsToViews: true, @@ -2217,14 +2486,14 @@ func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { "Title": "Hello, World!", }) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) app := New(Config{ PassLocalsToViews: true, Views: engine, @@ -2234,6 +2503,7 @@ func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) { c.BindVars(Map{ "Title": "Hello, World!", }) + require.Equal(b, nil, err) c.Locals("Summary", "Test") b.ReportAllocs() @@ -2243,61 +2513,14 @@ func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) { err = c.Render("template.tmpl", Map{}) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, "

Hello, World! Test

", string(c.Response().Body())) -} - -func Benchmark_Ctx_RedirectToRoute(b *testing.B) { - app := New() - app.Get("/user/:name", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - - c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) - - b.ReportAllocs() - b.ResetTimer() - - for n := 0; n < b.N; n++ { - c.RedirectToRoute("user", Map{ - "name": "fiber", - }) - } - - utils.AssertEqual(b, 302, c.Response().StatusCode()) - utils.AssertEqual(b, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) -} - -func Benchmark_Ctx_RedirectToRouteWithQueries(b *testing.B) { - app := New() - app.Get("/user/:name", func(c Ctx) error { - return c.JSON(c.Params("name")) - }).Name("user") - - c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) - - b.ReportAllocs() - b.ResetTimer() - - for n := 0; n < b.N; n++ { - c.RedirectToRoute("user", Map{ - "name": "fiber", - "queries": map[string]string{"a": "a", "b": "b"}, - }) - } - - utils.AssertEqual(b, 302, c.Response().StatusCode()) - // analysis of query parameters with url parsing, since a map pass is always randomly ordered - location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) - utils.AssertEqual(b, nil, err, "url.Parse(location)") - utils.AssertEqual(b, "/user/fiber", location.Path) - utils.AssertEqual(b, url.Values{"a": []string{"a"}, "b": []string{"b"}}, location.Query()) + require.NoError(b, err) + require.Equal(b, "

Hello, World! Test

", string(c.Response().Body())) } func Benchmark_Ctx_RenderLocals(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) app := New(Config{ PassLocalsToViews: true, }) @@ -2313,14 +2536,14 @@ func Benchmark_Ctx_RenderLocals(b *testing.B) { err = c.Render("index.tmpl", Map{}) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, "

Hello, World!

", string(c.Response().Body())) } func Benchmark_Ctx_RenderBindVars(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) app := New() app.config.Views = engine c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2328,6 +2551,7 @@ func Benchmark_Ctx_RenderBindVars(b *testing.B) { c.BindVars(Map{ "Title": "Hello, World!", }) + require.Equal(b, nil, err) b.ReportAllocs() b.ResetTimer() @@ -2336,8 +2560,8 @@ func Benchmark_Ctx_RenderBindVars(b *testing.B) { err = c.Render("index.tmpl", Map{}) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, "

Hello, World!

", string(c.Response().Body())) } // go test -run Test_Ctx_RestartRouting @@ -2352,9 +2576,9 @@ func Test_Ctx_RestartRouting(t *testing.T) { return nil }) resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, 3, calls, "Number of calls") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, 3, calls, "Number of calls") } // go test -run Test_Ctx_RestartRoutingWithChangedPath @@ -2377,10 +2601,10 @@ func Test_Ctx_RestartRoutingWithChangedPath(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/old", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, false, executedOldHandler, "Executed old handler") - utils.AssertEqual(t, true, executedNewHandler, "Executed new handler") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.False(t, executedOldHandler, "Executed old handler") + require.True(t, executedNewHandler, "Executed new handler") } // go test -run Test_Ctx_RestartRoutingWithChangedPathAnd404 @@ -2399,12 +2623,13 @@ func Test_Ctx_RestartRoutingWithChangedPathAndCatchAll(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/old", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") } type testTemplateEngine struct { templates *template.Template + path string } func (t *testTemplateEngine) Render(w io.Writer, name string, bind any, layout ...string) error { @@ -2416,14 +2641,17 @@ func (t *testTemplateEngine) Render(w io.Writer, name string, bind any, layout . } func (t *testTemplateEngine) Load() error { - t.templates = template.Must(template.ParseGlob("./.github/testdata/*.tmpl")) + if t.path == "" { + t.path = "testdata" + } + t.templates = template.Must(template.ParseGlob("./.github/" + t.path + "/*.tmpl")) return nil } // go test -run Test_Ctx_Render_Engine func Test_Ctx_Render_Engine(t *testing.T) { engine := &testTemplateEngine{} - engine.Load() + require.Equal(t, nil, engine.Load()) app := New() app.config.Views = engine c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2431,14 +2659,14 @@ func Test_Ctx_Render_Engine(t *testing.T) { err := c.Render("index.tmpl", Map{ "Title": "Hello, World!", }) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } // go test -run Test_Ctx_Render_Engine_With_View_Layout func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) { engine := &testTemplateEngine{} - engine.Load() + require.Equal(t, nil, engine.Load()) app := New(Config{ViewsLayout: "main.tmpl"}) app.config.Views = engine c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2446,15 +2674,15 @@ func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) { err := c.Render("index.tmpl", Map{ "Title": "Hello, World!", }) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "

Hello, World!

I'm main

", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "

Hello, World!

I'm main

", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Render_Engine -benchmem -count=4 func Benchmark_Ctx_Render_Engine(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() - utils.AssertEqual(b, nil, err) + require.NoError(b, err) app := New() app.config.Views = engine c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2466,8 +2694,8 @@ func Benchmark_Ctx_Render_Engine(b *testing.B) { "Title": "Hello, World!", }) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, "

Hello, World!

", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4 @@ -2478,23 +2706,51 @@ func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) { app.Get("/user/:name", func(c Ctx) error { return c.SendString(c.Params("name")) }).Name("User") + + var err error + var location string for n := 0; n < b.N; n++ { - c.getLocationFromRoute(app.GetRoute("User"), Map{"name": "fiber"}) + location, err = c.getLocationFromRoute(app.GetRoute("User"), Map{"name": "fiber"}) } + require.Equal(b, "/user/fiber", location) + require.Equal(b, nil, err) + } // go test -run Test_Ctx_Get_Location_From_Route_name func Test_Ctx_Get_Location_From_Route_name(t *testing.T) { - app := New() - c := app.NewCtx(&fasthttp.RequestCtx{}) + t.Run("case insensitive", func(t *testing.T) { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + app.Get("/user/:name", func(c Ctx) error { + return c.SendString(c.Params("name")) + }).Name("User") - app.Get("/user/:name", func(c Ctx) error { - return c.SendString(c.Params("name")) - }).Name("User") + location, err := c.GetRouteURL("User", Map{"name": "fiber"}) + require.NoError(t, err) + require.Equal(t, "/user/fiber", location) + + location, err = c.GetRouteURL("User", Map{"Name": "fiber"}) + require.NoError(t, err) + require.Equal(t, "/user/fiber", location) + }) - location, err := c.GetRouteURL("User", Map{"name": "fiber"}) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "/user/fiber", location) + t.Run("case sensitive", func(t *testing.T) { + app := New(Config{CaseSensitive: true}) + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + app.Get("/user/:name", func(c Ctx) error { + return c.SendString(c.Params("name")) + }).Name("User") + + location, err := c.GetRouteURL("User", Map{"name": "fiber"}) + require.NoError(t, err) + require.Equal(t, "/user/fiber", location) + + location, err = c.GetRouteURL("User", Map{"Name": "fiber"}) + require.NoError(t, err) + require.Equal(t, "/user/", location) + }) } // go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy @@ -2511,8 +2767,8 @@ func Test_Ctx_Get_Location_From_Route_name_Optional_greedy(t *testing.T) { "*1": "sms", "*2": "test-msg", }) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "/23456789/sms/send/test-msg", location) + require.NoError(t, err) + require.Equal(t, "/23456789/sms/send/test-msg", location) } // go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param @@ -2528,8 +2784,8 @@ func Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param(t *testing. "phone": "23456789", "*": "sms", }) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "/23456789/sms/send", location) + require.NoError(t, err) + require.Equal(t, "/23456789/sms/send", location) } type errorTemplateEngine struct{} @@ -2547,7 +2803,7 @@ func Test_Ctx_Render_Engine_Error(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) err := c.Render("index.tmpl", nil) - utils.AssertEqual(t, false, err == nil) + require.False(t, err == nil) } // go test -run Test_Ctx_Render_Go_Template @@ -2555,22 +2811,22 @@ func Test_Ctx_Render_Go_Template(t *testing.T) { t.Parallel() file, err := os.CreateTemp(os.TempDir(), "fiber") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) defer os.Remove(file.Name()) _, err = file.Write([]byte("template")) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) err = file.Close() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) err = c.Render(file.Name(), nil) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "template", string(c.Response().Body())) + require.NoError(t, err) + require.Equal(t, "template", string(c.Response().Body())) } // go test -run Test_Ctx_Send @@ -2579,10 +2835,10 @@ func Test_Ctx_Send(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Send([]byte("Hello, World")) - c.Send([]byte("Don't crash please")) - c.Send([]byte("1337")) - utils.AssertEqual(t, "1337", string(c.Response().Body())) + require.NoError(t, c.Send([]byte("Hello, World"))) + require.NoError(t, c.Send([]byte("Don't crash please"))) + require.NoError(t, c.Send([]byte("1337"))) + require.Equal(t, "1337", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Send -benchmem -count=4 @@ -2593,10 +2849,13 @@ func Benchmark_Ctx_Send(b *testing.B) { byt := []byte("Hello, World!") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Send(byt) + err = c.Send(byt) } - utils.AssertEqual(b, "Hello, World!", string(c.Response().Body())) + require.NoError(b, err) + require.Equal(b, "Hello, World!", string(c.Response().Body())) } // go test -run Test_Ctx_SendStatus @@ -2605,9 +2864,10 @@ func Test_Ctx_SendStatus(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - c.SendStatus(415) - utils.AssertEqual(t, 415, c.Response().StatusCode()) - utils.AssertEqual(t, "Unsupported Media Type", string(c.Response().Body())) + err := c.SendStatus(415) + require.NoError(t, err) + require.Equal(t, 415, c.Response().StatusCode()) + require.Equal(t, "Unsupported Media Type", string(c.Response().Body())) } // go test -run Test_Ctx_SendString @@ -2616,8 +2876,9 @@ func Test_Ctx_SendString(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - c.SendString("Don't crash please") - utils.AssertEqual(t, "Don't crash please", string(c.Response().Body())) + err := c.SendString("Don't crash please") + require.NoError(t, err) + require.Equal(t, "Don't crash please", string(c.Response().Body())) } // go test -run Test_Ctx_SendStream @@ -2627,18 +2888,13 @@ func Test_Ctx_SendStream(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.SendStream(bytes.NewReader([]byte("Don't crash please"))) - utils.AssertEqual(t, "Don't crash please", string(c.Response().Body())) + require.Equal(t, "Don't crash please", string(c.Response().Body())) c.SendStream(bytes.NewReader([]byte("Don't crash please")), len([]byte("Don't crash please"))) - utils.AssertEqual(t, "Don't crash please", string(c.Response().Body())) + require.Equal(t, "Don't crash please", string(c.Response().Body())) c.SendStream(bufio.NewReader(bytes.NewReader([]byte("Hello bufio")))) - utils.AssertEqual(t, "Hello bufio", string(c.Response().Body())) - - file, err := os.Open("./.github/index.html") - utils.AssertEqual(t, nil, err) - c.SendStream(bufio.NewReader(file)) - utils.AssertEqual(t, true, (c.Response().Header.ContentLength() > 200)) + require.Equal(t, "Hello bufio", string(c.Response().Body())) } // go test -run Test_Ctx_Set @@ -2651,9 +2907,9 @@ func Test_Ctx_Set(t *testing.T) { c.Set("X-2", "2") c.Set("X-3", "3") c.Set("X-3", "1337") - utils.AssertEqual(t, "1", string(c.Response().Header.Peek("x-1"))) - utils.AssertEqual(t, "2", string(c.Response().Header.Peek("x-2"))) - utils.AssertEqual(t, "1337", string(c.Response().Header.Peek("x-3"))) + require.Equal(t, "1", string(c.Response().Header.Peek("x-1"))) + require.Equal(t, "2", string(c.Response().Header.Peek("x-2"))) + require.Equal(t, "1337", string(c.Response().Header.Peek("x-3"))) } // go test -run Test_Ctx_Set_Splitter @@ -2664,11 +2920,11 @@ func Test_Ctx_Set_Splitter(t *testing.T) { c.Set("Location", "foo\r\nSet-Cookie:%20SESSIONID=MaliciousValue\r\n") h := string(c.Response().Header.Peek("Location")) - utils.AssertEqual(t, false, strings.Contains(h, "\r\n"), h) + require.False(t, strings.Contains(h, "\r\n"), h) c.Set("Location", "foo\nSet-Cookie:%20SESSIONID=MaliciousValue\n") h = string(c.Response().Header.Peek("Location")) - utils.AssertEqual(t, false, strings.Contains(h, "\n"), h) + require.False(t, strings.Contains(h, "\n"), h) } // go test -v -run=^$ -bench=Benchmark_Ctx_Set -benchmem -count=4 @@ -2691,10 +2947,11 @@ func Test_Ctx_Status(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Status(400) - utils.AssertEqual(t, 400, c.Response().StatusCode()) - c.Status(415).Send([]byte("Hello, World")) - utils.AssertEqual(t, 415, c.Response().StatusCode()) - utils.AssertEqual(t, "Hello, World", string(c.Response().Body())) + require.Equal(t, 400, c.Response().StatusCode()) + err := c.Status(415).Send([]byte("Hello, World")) + require.NoError(t, err) + require.Equal(t, 415, c.Response().StatusCode()) + require.Equal(t, "Hello, World", string(c.Response().Body())) } // go test -run Test_Ctx_Type @@ -2704,16 +2961,16 @@ func Test_Ctx_Type(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Type(".json") - utils.AssertEqual(t, "application/json", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "application/json", string(c.Response().Header.Peek("Content-Type"))) c.Type("json", "utf-8") - utils.AssertEqual(t, "application/json; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "application/json; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) c.Type(".html") - utils.AssertEqual(t, "text/html", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "text/html", string(c.Response().Header.Peek("Content-Type"))) c.Type("html", "utf-8") - utils.AssertEqual(t, "text/html; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "text/html; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Type -benchmem -count=4 @@ -2751,7 +3008,7 @@ func Test_Ctx_Vary(t *testing.T) { c.Vary("Origin") c.Vary("User-Agent") c.Vary("Accept-Encoding", "Accept") - utils.AssertEqual(t, "Origin, User-Agent, Accept-Encoding, Accept", string(c.Response().Header.Peek("Vary"))) + require.Equal(t, "Origin, User-Agent, Accept-Encoding, Accept", string(c.Response().Header.Peek("Vary"))) } // go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4 @@ -2774,7 +3031,7 @@ func Test_Ctx_Write(t *testing.T) { c.Write([]byte("Hello, ")) c.Write([]byte("World!")) - utils.AssertEqual(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Write -benchmem -count=4 @@ -2785,9 +3042,12 @@ func Benchmark_Ctx_Write(b *testing.B) { byt := []byte("Hello, World!") b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Write(byt) + _, err = c.Write(byt) } + require.Equal(b, nil, err) } // go test -run Test_Ctx_Writef @@ -2797,8 +3057,9 @@ func Test_Ctx_Writef(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) world := "World!" - c.Writef("Hello, %s", world) - utils.AssertEqual(t, "Hello, World!", string(c.Response().Body())) + _, err := c.Writef("Hello, %s", world) + require.NoError(t, err) + require.Equal(t, "Hello, World!", string(c.Response().Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4 @@ -2809,9 +3070,12 @@ func Benchmark_Ctx_Writef(b *testing.B) { world := "World!" b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.Writef("Hello, %s", world) + _, err = c.Writef("Hello, %s", world) } + require.Equal(b, nil, err) } // go test -run Test_Ctx_WriteString @@ -2822,7 +3086,7 @@ func Test_Ctx_WriteString(t *testing.T) { c.WriteString("Hello, ") c.WriteString("World!") - utils.AssertEqual(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Response().Body())) } // go test -run Test_Ctx_XHR @@ -2832,7 +3096,7 @@ func Test_Ctx_XHR(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXRequestedWith, "XMLHttpRequest") - utils.AssertEqual(t, true, c.XHR()) + require.True(t, c.XHR()) } // go test -run=^$ -bench=Benchmark_Ctx_XHR -benchmem -count=4 @@ -2847,7 +3111,7 @@ func Benchmark_Ctx_XHR(b *testing.B) { for n := 0; n < b.N; n++ { equal = c.XHR() } - utils.AssertEqual(b, true, equal) + require.True(b, equal) } // go test -v -run=^$ -bench=Benchmark_Ctx_SendString_B -benchmem -count=4 @@ -2858,10 +3122,13 @@ func Benchmark_Ctx_SendString_B(b *testing.B) { body := "Hello, world!" b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { - c.SendString(body) + err = c.SendString(body) } - utils.AssertEqual(b, []byte("Hello, world!"), c.Response().Body()) + require.NoError(b, err) + require.Equal(b, []byte("Hello, world!"), c.Response().Body()) } // go test -run Test_Ctx_BodyStreamWriter @@ -2900,17 +3167,20 @@ func Benchmark_Ctx_BodyStreamWriter(b *testing.B) { user := []byte(`{"name":"john"}`) b.ReportAllocs() b.ResetTimer() + + var err error for n := 0; n < b.N; n++ { ctx.ResetBody() ctx.SetBodyStreamWriter(func(w *bufio.Writer) { for i := 0; i < 10; i++ { - w.Write(user) + _, err = w.Write(user) if err := w.Flush(); err != nil { return } } }) } + require.Equal(b, nil, err) } func Test_Ctx_String(t *testing.T) { @@ -2919,7 +3189,7 @@ func Test_Ctx_String(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, "#0000000000000000 - 0.0.0.0:0 <-> 0.0.0.0:0 - GET http:///", c.String()) + require.Equal(t, "#0000000000000000 - 0.0.0.0:0 <-> 0.0.0.0:0 - GET http:///", c.String()) } func TestCtx_ParamsInt(t *testing.T) { @@ -2933,7 +3203,7 @@ func TestCtx_ParamsInt(t *testing.T) { // For the user id I will use the number 1111, so I should be able to get the number // 1111 from the Ctx app.Get("/test/:user", func(c Ctx) error { - // utils.AssertEqual(t, "john", c.Params("user")) + // require.Equal(t, "john", c.Params("user")) num, err := c.ParamsInt("user") @@ -2953,7 +3223,7 @@ func TestCtx_ParamsInt(t *testing.T) { // In this test case, there will be a bad request where the expected number is NOT // a number in the path app.Get("/testnoint/:user", func(c Ctx) error { - // utils.AssertEqual(t, "john", c.Params("user")) + // require.Equal(t, "john", c.Params("user")) num, err := c.ParamsInt("user") @@ -2973,7 +3243,7 @@ func TestCtx_ParamsInt(t *testing.T) { // For the user id I will use the number 2222, so I should be able to get the number // 2222 from the Ctx even when the default value is specified app.Get("/testignoredefault/:user", func(c Ctx) error { - // utils.AssertEqual(t, "john", c.Params("user")) + // require.Equal(t, "john", c.Params("user")) num, err := c.ParamsInt("user", 1111) @@ -2993,7 +3263,7 @@ func TestCtx_ParamsInt(t *testing.T) { // In this test case, there will be a bad request where the expected number is NOT // a number in the path, default value of 1111 should be used instead app.Get("/testdefault/:user", func(c Ctx) error { - // utils.AssertEqual(t, "john", c.Params("user")) + // require.Equal(t, "john", c.Params("user")) num, err := c.ParamsInt("user", 1111) @@ -3010,10 +3280,17 @@ func TestCtx_ParamsInt(t *testing.T) { return nil }) - app.Test(httptest.NewRequest(MethodGet, "/test/1111", nil)) - app.Test(httptest.NewRequest(MethodGet, "/testnoint/xd", nil)) - app.Test(httptest.NewRequest(MethodGet, "/testignoredefault/2222", nil)) - app.Test(httptest.NewRequest(MethodGet, "/testdefault/xd", nil)) + _, err := app.Test(httptest.NewRequest(MethodGet, "/test/1111", nil)) + require.Equal(t, nil, err) + + _, err = app.Test(httptest.NewRequest(MethodGet, "/testnoint/xd", nil)) + require.Equal(t, nil, err) + + _, err = app.Test(httptest.NewRequest(MethodGet, "/testignoredefault/2222", nil)) + require.Equal(t, nil, err) + + _, err = app.Test(httptest.NewRequest(MethodGet, "/testdefault/xd", nil)) + require.Equal(t, nil, err) } // go test -run Test_Ctx_GetRespHeader @@ -3023,8 +3300,8 @@ func Test_Ctx_GetRespHeader(t *testing.T) { c.Set("test", "Hello, World 👋!") c.Response().Header.Set(HeaderContentType, "application/json") - utils.AssertEqual(t, c.GetRespHeader("test"), "Hello, World 👋!") - utils.AssertEqual(t, c.GetRespHeader(HeaderContentType), "application/json") + require.Equal(t, c.GetRespHeader("test"), "Hello, World 👋!") + require.Equal(t, c.GetRespHeader(HeaderContentType), "application/json") } // go test -run Test_Ctx_IsFromLocal @@ -3035,7 +3312,7 @@ func Test_Ctx_IsFromLocal(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, true, c.IsFromLocal()) + require.True(t, c.IsFromLocal()) } // This is a test for "0.0.0.0" { @@ -3043,7 +3320,7 @@ func Test_Ctx_IsFromLocal(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.0") - utils.AssertEqual(t, true, c.IsFromLocal()) + require.True(t, c.IsFromLocal()) } // This is a test for "127.0.0.1" @@ -3052,7 +3329,7 @@ func Test_Ctx_IsFromLocal(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") - utils.AssertEqual(t, true, c.IsFromLocal()) + require.True(t, c.IsFromLocal()) } // This is a test for "localhost" @@ -3060,7 +3337,7 @@ func Test_Ctx_IsFromLocal(t *testing.T) { app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) - utils.AssertEqual(t, true, c.IsFromLocal()) + require.True(t, c.IsFromLocal()) } // This is testing "::1", it is the compressed format IPV6 loopback address 0:0:0:0:0:0:0:1. @@ -3070,7 +3347,7 @@ func Test_Ctx_IsFromLocal(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "::1") - utils.AssertEqual(t, true, c.IsFromLocal()) + require.True(t, c.IsFromLocal()) } { @@ -3078,6 +3355,6 @@ func Test_Ctx_IsFromLocal(t *testing.T) { c := app.NewCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90") - utils.AssertEqual(t, false, c.IsFromLocal()) + require.False(t, c.IsFromLocal()) } } diff --git a/error.go b/error.go index 87a6af38c9..66b431cb0f 100644 --- a/error.go +++ b/error.go @@ -2,23 +2,33 @@ package fiber import ( errors "encoding/json" - goErrors "errors" + stdErrors "errors" "github.com/gofiber/fiber/v3/internal/schema" ) +// Graceful shutdown errors +var ( + ErrGracefulTimeout = stdErrors.New("shutdown: graceful timeout has been reached, exiting") +) + +// Fiber redirection errors +var ( + ErrRedirectBackNoFallback = NewError(StatusInternalServerError, "Referer not found, you have to enter fallback URL for redirection.") +) + // Range errors var ( - ErrRangeMalformed = goErrors.New("range: malformed range header string") - ErrRangeUnsatisfiable = goErrors.New("range: unsatisfiable range") + ErrRangeMalformed = stdErrors.New("range: malformed range header string") + ErrRangeUnsatisfiable = stdErrors.New("range: unsatisfiable range") ) // Binder errors -var ErrCustomBinderNotFound = goErrors.New("binder: custom binder not found, please be sure to enter the right name") +var ErrCustomBinderNotFound = stdErrors.New("binder: custom binder not found, please be sure to enter the right name") // gorilla/schema errors type ( - // Conversion error exposes the internal schema.ConversionError for public use. + // ConversionError Conversion error exposes the internal schema.ConversionError for public use. ConversionError = schema.ConversionError // UnknownKeyError error exposes the internal schema.UnknownKeyError for public use. UnknownKeyError = schema.UnknownKeyError diff --git a/error_test.go b/error_test.go index ca78044faf..7fce3c12aa 100644 --- a/error_test.go +++ b/error_test.go @@ -7,61 +7,61 @@ import ( jerrors "encoding/json" "github.com/gofiber/fiber/v3/internal/schema" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func TestConversionError(t *testing.T) { ok := errors.As(ConversionError{}, &schema.ConversionError{}) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestUnknownKeyError(t *testing.T) { ok := errors.As(UnknownKeyError{}, &schema.UnknownKeyError{}) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestEmptyFieldError(t *testing.T) { ok := errors.As(EmptyFieldError{}, &schema.EmptyFieldError{}) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestMultiError(t *testing.T) { ok := errors.As(MultiError{}, &schema.MultiError{}) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestInvalidUnmarshalError(t *testing.T) { var e *jerrors.InvalidUnmarshalError ok := errors.As(&InvalidUnmarshalError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestMarshalerError(t *testing.T) { var e *jerrors.MarshalerError ok := errors.As(&MarshalerError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestSyntaxError(t *testing.T) { var e *jerrors.SyntaxError ok := errors.As(&SyntaxError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestUnmarshalTypeError(t *testing.T) { var e *jerrors.UnmarshalTypeError ok := errors.As(&UnmarshalTypeError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestUnsupportedTypeError(t *testing.T) { var e *jerrors.UnsupportedTypeError ok := errors.As(&UnsupportedTypeError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func TestUnsupportedValeError(t *testing.T) { var e *jerrors.UnsupportedValueError ok := errors.As(&UnsupportedValueError{}, &e) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } diff --git a/go.mod b/go.mod index b6575c4b55..3f7e1a2764 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,28 @@ module github.com/gofiber/fiber/v3 -go 1.18 +go 1.19 require ( + github.com/gofiber/utils v1.0.0 github.com/google/uuid v1.3.0 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 - github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 + github.com/stretchr/testify v1.8.0 github.com/tinylib/msgp v1.1.6 github.com/valyala/bytebufferpool v1.0.0 - github.com/valyala/fasthttp v1.39.0 + github.com/valyala/fasthttp v1.41.0 github.com/valyala/fasttemplate v1.2.1 ) require ( github.com/andybalholm/brotli v1.0.4 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/klauspost/compress v1.15.12 // indirect github.com/philhofer/fwd v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect + golang.org/x/sys v0.1.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4a18053ba1..5016a3a728 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,38 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofiber/utils v1.0.0 h1:goxlTmYidOhsCvuZuTLzT224DELpnz9c/+iw5eN9FJw= +github.com/gofiber/utils v1.0.0/go.mod h1:RYennBgjLkuNtU+dxg7QgBEU8tmixFQHQ2GE1ioZlxw= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d h1:ICMDEgNgR5xFW6ZDeMKTtmh07YiLr7GkDw897I2DwKg= -github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d/go.mod h1:jrsy/bTK2n5uybo7bAvtLGzmuzAbxp+nKS8bzgrZURE= -github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= 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.39.0 h1:lW8mGeM7yydOqZKmwyMTaz/PH/A+CLgtmmcjv+OORfU= -github.com/valyala/fasthttp v1.39.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= +github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= @@ -36,7 +47,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -45,11 +56,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -62,3 +72,8 @@ golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4f golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/group.go b/group.go index af5e6ffa91..71bad4f8ae 100644 --- a/group.go +++ b/group.go @@ -8,7 +8,6 @@ import ( "fmt" "reflect" "strings" - "sync/atomic" ) // Group struct @@ -19,33 +18,7 @@ type Group struct { Prefix string } -// Mount attaches another app instance as a sub-router along a routing path. -// It's very useful to split up a large API as many independent routers and -// compose them as a single service using Mount. -func (grp *Group) mount(prefix string, sub *App) Router { - stack := sub.Stack() - groupPath := getGroupPath(grp.Prefix, prefix) - groupPath = strings.TrimRight(groupPath, "/") - if groupPath == "" { - groupPath = "/" - } - for m := range stack { - for r := range stack[m] { - route := grp.app.copyRoute(stack[m][r]) - grp.app.addRoute(route.Method, grp.app.addPrefixToRoute(groupPath, route)) - } - } - // Support for configs of mounted-apps and sub-mounted-apps - for mountedPrefixes, subApp := range sub.appList { - grp.app.appList[groupPath+mountedPrefixes] = subApp - subApp.init() - } - - atomic.AddUint32(&grp.app.handlersCount, sub.handlersCount) - return grp -} - -// Assign name to specific route. +// Name Assign name to specific route. func (grp *Group) Name(name string) Router { grp.app.mutex.Lock() if strings.HasPrefix(grp.Prefix, grp.app.latestGroup.Prefix) { @@ -80,14 +53,15 @@ func (grp *Group) Name(name string) Router { // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... func (grp *Group) Use(args ...any) Router { prefix := "" - var subApp *App + var subApps []*App var handlers []Handler + for i := 0; i < len(args); i++ { switch arg := args[i].(type) { case string: prefix = arg case *App: - subApp = arg + subApps = append(subApps, arg) case Handler: handlers = append(handlers, arg) default: @@ -95,8 +69,10 @@ func (grp *Group) Use(args ...any) Router { } } - if subApp != nil { - grp.mount(prefix, subApp) + if len(subApps) > 0 { + for _, subApp := range subApps { + grp.mount(prefix, subApp) + } return grp } @@ -108,7 +84,7 @@ func (grp *Group) Use(args ...any) Router { // of the specified resource. Requests using GET should only retrieve data. func (grp *Group) Get(path string, handlers ...Handler) Router { path = getGroupPath(grp.Prefix, path) - return grp.app.Add(MethodHead, path, handlers...).Add(MethodGet, path, handlers...) + return grp.app.Add(MethodGet, path, handlers...) } // Head registers a route for HEAD methods that asks for a response identical @@ -190,15 +166,9 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (grp *Group) Route(prefix string, fn func(router Router), name ...string) Router { +func (grp *Group) Route(path string) Register { // Create new group - group := grp.Group(prefix) - if len(name) > 0 { - group.Name(name[0]) - } - - // Define routes - fn(group) + register := &Registering{app: grp.app, path: getGroupPath(grp.Prefix, path)} - return group + return register } diff --git a/helpers.go b/helpers.go index 3766e301be..db7efc54c7 100644 --- a/helpers.go +++ b/helpers.go @@ -17,42 +17,10 @@ import ( "time" "unsafe" - "github.com/gofiber/fiber/v3/utils" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" ) -/* #nosec */ -// lnMetadata will close the listener and return the addr and tls config -func lnMetadata(network string, ln net.Listener) (addr string, cfg *tls.Config) { - // Get addr - addr = ln.Addr().String() - - // Close listener - if err := ln.Close(); err != nil { - return - } - - // Wait for the listener to be closed - var closed bool - for i := 0; i < 10; i++ { - conn, err := net.DialTimeout(network, addr, 3*time.Second) - if err != nil || conn == nil { - closed = true - break - } - _ = conn.Close() - time.Sleep(100 * time.Millisecond) - } - if !closed { - panic("listener: " + addr + ": Only one usage of each socket address (protocol/network address/port) is normally permitted.") - } - - cfg = getTlsConfig(ln) - - return -} - /* #nosec */ // getTlsConfig returns a net listener's tls config func getTlsConfig(ln net.Listener) *tls.Config { @@ -215,7 +183,7 @@ func defaultString(value string, defaultValue []string) string { } func getGroupPath(prefix, path string) string { - if len(path) == 0 || path == "/" { + if len(path) == 0 { return prefix } @@ -223,7 +191,7 @@ func getGroupPath(prefix, path string) string { path = "/" + path } - return utils.TrimRight(prefix, '/') + path + return strings.TrimRight(prefix, "/") + path } // return valid offer for header negotiation @@ -238,7 +206,7 @@ func getOffer(header string, offers ...string) string { for len(header) > 0 && commaPos != -1 { commaPos = strings.IndexByte(header, ',') if commaPos != -1 { - spec = utils.Trim(header[:commaPos], ' ') + spec = strings.TrimSpace(header[:commaPos]) } else { spec = header } @@ -409,21 +377,25 @@ const ( // MIME types that are commonly used const ( - MIMETextXML = "text/xml" - MIMETextHTML = "text/html" - MIMETextPlain = "text/plain" - MIMEApplicationXML = "application/xml" - MIMEApplicationJSON = "application/json" + MIMETextXML = "text/xml" + MIMETextHTML = "text/html" + MIMETextPlain = "text/plain" + MIMETextJavaScript = "text/javascript" + MIMEApplicationXML = "application/xml" + MIMEApplicationJSON = "application/json" + // Deprecated: use MIMETextJavaScript instead MIMEApplicationJavaScript = "application/javascript" MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEOctetStream = "application/octet-stream" MIMEMultipartForm = "multipart/form-data" - MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" - MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" - MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" - MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" - MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" + MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" + MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" + MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" + MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" + MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" + MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" + // Deprecated: use MIMETextJavaScriptCharsetUTF8 instead MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" ) @@ -686,3 +658,24 @@ const ( CookieSameSiteStrictMode = "strict" CookieSameSiteNoneMode = "none" ) + +// Route Constraints +const ( + ConstraintInt = "int" + ConstraintBool = "bool" + ConstraintFloat = "float" + ConstraintAlpha = "alpha" + ConstraintGuid = "guid" + ConstraintMinLen = "minLen" + ConstraintMaxLen = "maxLen" + ConstraintLen = "len" + ConstraintBetweenLen = "betweenLen" + ConstraintMinLenLower = "minlen" + ConstraintMaxLenLower = "maxlen" + ConstraintBetweenLenLower = "betweenlen" + ConstraintMin = "min" + ConstraintMax = "max" + ConstraintRange = "range" + ConstraintDatetime = "datetime" + ConstraintRegex = "regex" +) diff --git a/helpers_test.go b/helpers_test.go index 0718f6bb3b..d7f658a88e 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -5,14 +5,13 @@ package fiber import ( - "crypto/tls" "fmt" - "net" "strings" "testing" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -20,7 +19,7 @@ func Test_Utils_UniqueRouteStack(t *testing.T) { route1 := &Route{} route2 := &Route{} route3 := &Route{} - utils.AssertEqual( + require.Equal( t, []*Route{ route1, @@ -40,32 +39,29 @@ func Test_Utils_UniqueRouteStack(t *testing.T) { route1, route2, route3, - }), - ) + })) + } func Test_Utils_getGroupPath(t *testing.T) { t.Parallel() res := getGroupPath("/v1", "/") - utils.AssertEqual(t, "/v1", res) + require.Equal(t, "/v1/", res) res = getGroupPath("/v1/", "/") - utils.AssertEqual(t, "/v1/", res) - - res = getGroupPath("/v1", "/") - utils.AssertEqual(t, "/v1", res) + require.Equal(t, "/v1/", res) res = getGroupPath("/", "/") - utils.AssertEqual(t, "/", res) + require.Equal(t, "/", res) res = getGroupPath("/v1/api/", "/") - utils.AssertEqual(t, "/v1/api/", res) + require.Equal(t, "/v1/api/", res) res = getGroupPath("/v1/api", "group") - utils.AssertEqual(t, "/v1/api/group", res) + require.Equal(t, "/v1/api/group", res) res = getGroupPath("/v1/api", "") - utils.AssertEqual(t, "/v1/api", res) + require.Equal(t, "/v1/api", res) } // go test -v -run=^$ -bench=Benchmark_Utils_ -benchmem -count=3 @@ -78,7 +74,7 @@ func Benchmark_Utils_getGroupPath(b *testing.B) { _ = getGroupPath("/v1", "/api") res = getGroupPath("/v1", "/api/register/:project") } - utils.AssertEqual(b, "/v1/api/register/:project", res) + require.Equal(b, "/v1/api/register/:project", res) } func Benchmark_Utils_Unescape(b *testing.B) { @@ -92,7 +88,7 @@ func Benchmark_Utils_Unescape(b *testing.B) { unescaped = utils.UnsafeString(pathBytes) } - utils.AssertEqual(b, "/créer", unescaped) + require.Equal(b, "/créer", unescaped) } func Test_Utils_Parse_Address(t *testing.T) { @@ -106,22 +102,22 @@ func Test_Utils_Parse_Address(t *testing.T) { for _, c := range testCases { host, port := parseAddr(c.addr) - utils.AssertEqual(t, c.host, host, "addr host") - utils.AssertEqual(t, c.port, port, "addr port") + require.Equal(t, c.host, host, "addr host") + require.Equal(t, c.port, port, "addr port") } } func Test_Utils_GetOffset(t *testing.T) { - utils.AssertEqual(t, "", getOffer("hello")) - utils.AssertEqual(t, "1", getOffer("", "1")) - utils.AssertEqual(t, "", getOffer("2", "1")) + require.Equal(t, "", getOffer("hello")) + require.Equal(t, "1", getOffer("", "1")) + require.Equal(t, "", getOffer("2", "1")) } func Test_Utils_TestConn_Deadline(t *testing.T) { conn := &testConn{} - utils.AssertEqual(t, nil, conn.SetDeadline(time.Time{})) - utils.AssertEqual(t, nil, conn.SetReadDeadline(time.Time{})) - utils.AssertEqual(t, nil, conn.SetWriteDeadline(time.Time{})) + require.Nil(t, conn.SetDeadline(time.Time{})) + require.Nil(t, conn.SetReadDeadline(time.Time{})) + require.Nil(t, conn.SetWriteDeadline(time.Time{})) } func Test_Utils_IsNoCache(t *testing.T) { @@ -141,8 +137,9 @@ func Test_Utils_IsNoCache(t *testing.T) { for _, c := range testCases { ok := isNoCache(c.string) - utils.AssertEqual(t, c.bool, ok, + require.Equal(t, c.bool, ok, fmt.Sprintf("want %t, got isNoCache(%s)=%t", c.bool, c.string, ok)) + } } @@ -157,49 +154,7 @@ func Benchmark_Utils_IsNoCache(b *testing.B) { _ = isNoCache("no-cache, public") ok = isNoCache("max-age=30, no-cache,public") } - utils.AssertEqual(b, true, ok) -} - -func Test_Utils_lnMetadata(t *testing.T) { - t.Run("closed listen", func(t *testing.T) { - ln, err := net.Listen(NetworkTCP, ":0") - utils.AssertEqual(t, nil, err) - - utils.AssertEqual(t, nil, ln.Close()) - - addr, config := lnMetadata(NetworkTCP, ln) - - utils.AssertEqual(t, ln.Addr().String(), addr) - utils.AssertEqual(t, true, config == nil) - }) - - t.Run("non tls", func(t *testing.T) { - ln, err := net.Listen(NetworkTCP, ":0") - - utils.AssertEqual(t, nil, err) - - addr, config := lnMetadata(NetworkTCP4, ln) - - utils.AssertEqual(t, ln.Addr().String(), addr) - utils.AssertEqual(t, true, config == nil) - }) - - t.Run("tls", func(t *testing.T) { - cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") - utils.AssertEqual(t, nil, err) - - config := &tls.Config{Certificates: []tls.Certificate{cer}} - - ln, err := net.Listen(NetworkTCP4, ":0") - utils.AssertEqual(t, nil, err) - - ln = tls.NewListener(ln, config) - - addr, config := lnMetadata(NetworkTCP4, ln) - - utils.AssertEqual(t, ln.Addr().String(), addr) - utils.AssertEqual(t, true, config != nil) - }) + require.True(b, ok) } // go test -v -run=^$ -bench=Benchmark_SlashRecognition -benchmem -count=4 @@ -213,7 +168,7 @@ func Benchmark_SlashRecognition(b *testing.B) { result = true } } - utils.AssertEqual(b, true, result) + require.True(b, result) }) b.Run("forEach", func(b *testing.B) { result = false @@ -226,7 +181,7 @@ func Benchmark_SlashRecognition(b *testing.B) { } } } - utils.AssertEqual(b, true, result) + require.True(b, result) }) b.Run("IndexRune", func(b *testing.B) { result = false @@ -234,7 +189,7 @@ func Benchmark_SlashRecognition(b *testing.B) { for i := 0; i < b.N; i++ { result = IndexRune(search, c) } - utils.AssertEqual(b, true, result) + require.True(b, result) }) } diff --git a/hooks.go b/hooks.go index b13d491b98..b2766a8cab 100644 --- a/hooks.go +++ b/hooks.go @@ -1,14 +1,17 @@ package fiber -// Handlers define a function to create hooks for Fiber. +// OnRouteHandler Handlers define a function to create hooks for Fiber. type OnRouteHandler = func(Route) error type OnNameHandler = OnRouteHandler type OnGroupHandler = func(Group) error type OnGroupNameHandler = OnGroupHandler type OnListenHandler = func() error type OnShutdownHandler = OnListenHandler +type OnForkHandler = func(int) error +type OnMountHandler = func(*App) error -type hooks struct { +// Hooks is a struct to use it with App. +type Hooks struct { // Embed app app *App @@ -19,10 +22,12 @@ type hooks struct { onGroupName []OnGroupNameHandler onListen []OnListenHandler onShutdown []OnShutdownHandler + onFork []OnForkHandler + onMount []OnMountHandler } -func newHooks(app *App) *hooks { - return &hooks{ +func newHooks(app *App) *Hooks { + return &Hooks{ app: app, onRoute: make([]OnRouteHandler, 0), onGroup: make([]OnGroupHandler, 0), @@ -30,12 +35,14 @@ func newHooks(app *App) *hooks { onName: make([]OnNameHandler, 0), onListen: make([]OnListenHandler, 0), onShutdown: make([]OnShutdownHandler, 0), + onFork: make([]OnForkHandler, 0), + onMount: make([]OnMountHandler, 0), } } // OnRoute is a hook to execute user functions on each route registeration. // Also you can get route properties by route parameter. -func (h *hooks) OnRoute(handler ...OnRouteHandler) { +func (h *Hooks) OnRoute(handler ...OnRouteHandler) { h.app.mutex.Lock() h.onRoute = append(h.onRoute, handler...) h.app.mutex.Unlock() @@ -45,7 +52,7 @@ func (h *hooks) OnRoute(handler ...OnRouteHandler) { // Also you can get route properties by route parameter. // // WARN: OnName only works with naming routes, not groups. -func (h *hooks) OnName(handler ...OnNameHandler) { +func (h *Hooks) OnName(handler ...OnNameHandler) { h.app.mutex.Lock() h.onName = append(h.onName, handler...) h.app.mutex.Unlock() @@ -53,7 +60,7 @@ func (h *hooks) OnName(handler ...OnNameHandler) { // OnGroup is a hook to execute user functions on each group registeration. // Also you can get group properties by group parameter. -func (h *hooks) OnGroup(handler ...OnGroupHandler) { +func (h *Hooks) OnGroup(handler ...OnGroupHandler) { h.app.mutex.Lock() h.onGroup = append(h.onGroup, handler...) h.app.mutex.Unlock() @@ -63,27 +70,49 @@ func (h *hooks) OnGroup(handler ...OnGroupHandler) { // Also you can get group properties by group parameter. // // WARN: OnGroupName only works with naming groups, not routes. -func (h *hooks) OnGroupName(handler ...OnGroupNameHandler) { +func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) { h.app.mutex.Lock() h.onGroupName = append(h.onGroupName, handler...) h.app.mutex.Unlock() } // OnListen is a hook to execute user functions on Listen, ListenTLS, Listener. -func (h *hooks) OnListen(handler ...OnListenHandler) { +func (h *Hooks) OnListen(handler ...OnListenHandler) { h.app.mutex.Lock() h.onListen = append(h.onListen, handler...) h.app.mutex.Unlock() } // OnShutdown is a hook to execute user functions after Shutdown. -func (h *hooks) OnShutdown(handler ...OnShutdownHandler) { +func (h *Hooks) OnShutdown(handler ...OnShutdownHandler) { h.app.mutex.Lock() h.onShutdown = append(h.onShutdown, handler...) h.app.mutex.Unlock() } -func (h *hooks) executeOnRouteHooks(route Route) error { +// OnFork is a hook to execute user function after fork process. +func (h *Hooks) OnFork(handler ...OnForkHandler) { + h.app.mutex.Lock() + h.onFork = append(h.onFork, handler...) + h.app.mutex.Unlock() +} + +// OnMount is a hook to execute user function after mounting process. +// The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter. +// It works for app and group mounting. +func (h *Hooks) OnMount(handler ...OnMountHandler) { + h.app.mutex.Lock() + h.onMount = append(h.onMount, handler...) + h.app.mutex.Unlock() +} + +func (h *Hooks) executeOnRouteHooks(route Route) error { + // Check mounting + if h.app.mountFields.mountPath != "" { + route.path = h.app.mountFields.mountPath + route.path + route.Path = route.path + } + for _, v := range h.onRoute { if err := v(route); err != nil { return err @@ -93,7 +122,12 @@ func (h *hooks) executeOnRouteHooks(route Route) error { return nil } -func (h *hooks) executeOnNameHooks(route Route) error { +func (h *Hooks) executeOnNameHooks(route Route) error { + // Check mounting + if h.app.mountFields.mountPath != "" { + route.path = h.app.mountFields.mountPath + route.path + route.Path = route.path + } for _, v := range h.onName { if err := v(route); err != nil { @@ -104,7 +138,12 @@ func (h *hooks) executeOnNameHooks(route Route) error { return nil } -func (h *hooks) executeOnGroupHooks(group Group) error { +func (h *Hooks) executeOnGroupHooks(group Group) error { + // Check mounting + if h.app.mountFields.mountPath != "" { + group.Prefix = h.app.mountFields.mountPath + group.Prefix + } + for _, v := range h.onGroup { if err := v(group); err != nil { return err @@ -114,7 +153,12 @@ func (h *hooks) executeOnGroupHooks(group Group) error { return nil } -func (h *hooks) executeOnGroupNameHooks(group Group) error { +func (h *Hooks) executeOnGroupNameHooks(group Group) error { + // Check mounting + if h.app.mountFields.mountPath != "" { + group.Prefix = h.app.mountFields.mountPath + group.Prefix + } + for _, v := range h.onGroupName { if err := v(group); err != nil { return err @@ -124,7 +168,7 @@ func (h *hooks) executeOnGroupNameHooks(group Group) error { return nil } -func (h *hooks) executeOnListenHooks() error { +func (h *Hooks) executeOnListenHooks() error { for _, v := range h.onListen { if err := v(); err != nil { return err @@ -134,8 +178,24 @@ func (h *hooks) executeOnListenHooks() error { return nil } -func (h *hooks) executeOnShutdownHooks() { +func (h *Hooks) executeOnShutdownHooks() { for _, v := range h.onShutdown { _ = v() } } + +func (h *Hooks) executeOnForkHooks(pid int) { + for _, v := range h.onFork { + _ = v(pid) + } +} + +func (h *Hooks) executeOnMountHooks(app *App) error { + for _, v := range h.onMount { + if err := v(app); err != nil { + return err + } + } + + return nil +} diff --git a/hooks_test.go b/hooks_test.go index 932de0bd83..b4cb642014 100644 --- a/hooks_test.go +++ b/hooks_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/bytebufferpool" ) @@ -20,7 +20,7 @@ func Test_Hook_OnRoute(t *testing.T) { app := New() app.Hooks().OnRoute(func(r Route) error { - utils.AssertEqual(t, "", r.Name) + require.Equal(t, "", r.Name) return nil }) @@ -33,6 +33,29 @@ func Test_Hook_OnRoute(t *testing.T) { app.Use("/sub", subApp) } +func Test_Hook_OnRoute_Mount(t *testing.T) { + t.Parallel() + + app := New() + subApp := New() + app.Use("/sub", subApp) + + subApp.Hooks().OnRoute(func(r Route) error { + require.Equal(t, "/sub/test", r.Path) + + return nil + }) + + app.Hooks().OnRoute(func(r Route) error { + require.Equal(t, "/", r.Path) + + return nil + }) + + app.Get("/", testSimpleHandler).Name("x") + subApp.Get("/test", testSimpleHandler) +} + func Test_Hook_OnName(t *testing.T) { t.Parallel() @@ -42,7 +65,8 @@ func Test_Hook_OnName(t *testing.T) { defer bytebufferpool.Put(buf) app.Hooks().OnName(func(r Route) error { - buf.WriteString(r.Name) + _, err := buf.WriteString(r.Name) + require.NoError(t, nil, err) return nil }) @@ -55,7 +79,7 @@ func Test_Hook_OnName(t *testing.T) { app.Use("/sub", subApp) - utils.AssertEqual(t, "index", buf.String()) + require.Equal(t, "index", buf.String()) } func Test_Hook_OnName_Error(t *testing.T) { @@ -64,7 +88,7 @@ func Test_Hook_OnName_Error(t *testing.T) { app := New() defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "unknown error", fmt.Sprintf("%v", err)) + require.Equal(t, "unknown error", fmt.Sprintf("%v", err)) } }() @@ -84,15 +108,33 @@ func Test_Hook_OnGroup(t *testing.T) { defer bytebufferpool.Put(buf) app.Hooks().OnGroup(func(g Group) error { - buf.WriteString(g.Prefix) - + _, err := buf.WriteString(g.Prefix) + require.NoError(t, nil, err) return nil }) grp := app.Group("/x").Name("x.") grp.Group("/a") - utils.AssertEqual(t, "/x/x/a", buf.String()) + require.Equal(t, "/x/x/a", buf.String()) +} + +func Test_Hook_OnGroup_Mount(t *testing.T) { + t.Parallel() + + app := New() + micro := New() + micro.Use("/john", app) + + app.Hooks().OnGroup(func(g Group) error { + require.Equal(t, "/john/v1", g.Prefix) + return nil + }) + + v1 := app.Group("/v1") + v1.Get("/doe", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) } func Test_Hook_OnGroupName(t *testing.T) { @@ -104,7 +146,8 @@ func Test_Hook_OnGroupName(t *testing.T) { defer bytebufferpool.Put(buf) app.Hooks().OnGroupName(func(g Group) error { - buf.WriteString(g.name) + _, err := buf.WriteString(g.name) + require.NoError(t, nil, err) return nil }) @@ -113,7 +156,7 @@ func Test_Hook_OnGroupName(t *testing.T) { grp.Get("/test", testSimpleHandler) grp.Get("/test2", testSimpleHandler) - utils.AssertEqual(t, "x.", buf.String()) + require.Equal(t, "x.", buf.String()) } func Test_Hook_OnGroupName_Error(t *testing.T) { @@ -122,7 +165,7 @@ func Test_Hook_OnGroupName_Error(t *testing.T) { app := New() defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "unknown error", fmt.Sprintf("%v", err)) + require.Equal(t, "unknown error", fmt.Sprintf("%v", err)) } }() @@ -143,36 +186,74 @@ func Test_Hook_OnShutdown(t *testing.T) { defer bytebufferpool.Put(buf) app.Hooks().OnShutdown(func() error { - buf.WriteString("shutdowning") + _, err := buf.WriteString("shutdowning") + require.NoError(t, nil, err) return nil }) - utils.AssertEqual(t, nil, app.Shutdown()) - utils.AssertEqual(t, "shutdowning", buf.String()) + require.Nil(t, app.Shutdown()) + require.Equal(t, "shutdowning", buf.String()) } func Test_Hook_OnListen(t *testing.T) { t.Parallel() - app := New(Config{ - DisableStartupMessage: true, - }) + app := New() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) app.Hooks().OnListen(func() error { - buf.WriteString("ready") + _, err := buf.WriteString("ready") + require.NoError(t, nil, err) return nil }) go func() { time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.Listen(":9000")) - utils.AssertEqual(t, "ready", buf.String()) + require.Nil(t, app.Listen(":9000", ListenConfig{DisableStartupMessage: true})) + require.Equal(t, "ready", buf.String()) +} + +func Test_Hook_OnHook(t *testing.T) { + // Reset test var + testPreforkMaster = true + testOnPrefork = true + + app := New() + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + app.Hooks().OnFork(func(pid int) error { + require.Equal(t, 1, pid) + return nil + }) + + require.Nil(t, app.prefork(":3000", nil, ListenConfig{DisableStartupMessage: true, EnablePrefork: true})) +} + +func Test_Hook_OnMount(t *testing.T) { + t.Parallel() + + app := New() + app.Get("/", testSimpleHandler).Name("x") + + subApp := New() + subApp.Get("/test", testSimpleHandler) + + subApp.Hooks().OnMount(func(parent *App) error { + require.Equal(t, parent.mountFields.mountPath, "") + + return nil + }) + + app.Use("/sub", subApp) } diff --git a/internal/memory/memory.go b/internal/memory/memory.go new file mode 100644 index 0000000000..befdf67fb9 --- /dev/null +++ b/internal/memory/memory.go @@ -0,0 +1,92 @@ +// Package memory Is a slight copy of the memory storage, but far from the storage interface it can not only work with bytes +// but directly store any kind of data without having to encode it each time, which gives a huge speed advantage +package memory + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/gofiber/utils" +) + +type Storage struct { + sync.RWMutex + data map[string]item // data +} + +type item struct { + // max value is 4294967295 -> Sun Feb 07 2106 06:28:15 GMT+0000 + e uint32 // exp + v interface{} // val +} + +func New() *Storage { + store := &Storage{ + data: make(map[string]item), + } + utils.StartTimeStampUpdater() + go store.gc(1 * time.Second) + return store +} + +// Get value by key +func (s *Storage) Get(key string) interface{} { + s.RLock() + v, ok := s.data[key] + s.RUnlock() + if !ok || v.e != 0 && v.e <= atomic.LoadUint32(&utils.Timestamp) { + return nil + } + return v.v +} + +// Set key with value +func (s *Storage) Set(key string, val interface{}, ttl time.Duration) { + var exp uint32 + if ttl > 0 { + exp = uint32(ttl.Seconds()) + atomic.LoadUint32(&utils.Timestamp) + } + s.Lock() + s.data[key] = item{exp, val} + s.Unlock() +} + +// Delete key by key +func (s *Storage) Delete(key string) { + s.Lock() + delete(s.data, key) + s.Unlock() +} + +// Reset all keys +func (s *Storage) Reset() { + s.Lock() + s.data = make(map[string]item) + s.Unlock() +} + +func (s *Storage) gc(sleep time.Duration) { + ticker := time.NewTicker(sleep) + defer ticker.Stop() + var expired []string + + for { + select { + case <-ticker.C: + expired = expired[:0] + s.RLock() + for key, v := range s.data { + if v.e != 0 && v.e <= atomic.LoadUint32(&utils.Timestamp) { + expired = append(expired, key) + } + } + s.RUnlock() + s.Lock() + for i := range expired { + delete(s.data, expired[i]) + } + s.Unlock() + } + } +} diff --git a/internal/memory/memory_test.go b/internal/memory/memory_test.go new file mode 100644 index 0000000000..5bfeeef401 --- /dev/null +++ b/internal/memory/memory_test.go @@ -0,0 +1,82 @@ +package memory + +import ( + "testing" + "time" + + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" +) + +// go test -run Test_Memory -v -race + +func Test_Memory(t *testing.T) { + var store = New() + var ( + key = "john" + val interface{} = []byte("doe") + exp = 1 * time.Second + ) + + store.Set(key, val, 0) + store.Set(key, val, 0) + + result := store.Get(key) + require.Equal(t, val, result) + + result = store.Get("empty") + require.Equal(t, nil, result) + + store.Set(key, val, exp) + time.Sleep(1100 * time.Millisecond) + + result = store.Get(key) + require.Equal(t, nil, result) + + store.Set(key, val, 0) + result = store.Get(key) + require.Equal(t, val, result) + + store.Delete(key) + result = store.Get(key) + require.Equal(t, nil, result) + + store.Set("john", val, 0) + store.Set("doe", val, 0) + store.Reset() + + result = store.Get("john") + require.Equal(t, nil, result) + + result = store.Get("doe") + require.Equal(t, nil, result) +} + +// go test -v -run=^$ -bench=Benchmark_Memory -benchmem -count=4 +func Benchmark_Memory(b *testing.B) { + keyLength := 1000 + keys := make([]string, keyLength) + for i := 0; i < keyLength; i++ { + keys[i] = utils.UUID() + } + value := []byte("joe") + + ttl := 2 * time.Second + b.Run("fiber_memory", func(b *testing.B) { + d := New() + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + for _, key := range keys { + d.Set(key, value, ttl) + } + for _, key := range keys { + _ = d.Get(key) + } + for _, key := range keys { + d.Delete(key) + + } + } + }) +} diff --git a/internal/schema/decoder.go b/internal/schema/decoder.go index 9d44822202..b63c45e1dc 100644 --- a/internal/schema/decoder.go +++ b/internal/schema/decoder.go @@ -74,19 +74,19 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { } v = v.Elem() t := v.Type() - errors := MultiError{} + multiError := MultiError{} for path, values := range src { if parts, err := d.cache.parsePath(path, t); err == nil { if err = d.decode(v, path, parts, values); err != nil { - errors[path] = err + multiError[path] = err } } else if !d.ignoreUnknownKeys { - errors[path] = UnknownKeyError{Key: path} + multiError[path] = UnknownKeyError{Key: path} } } - errors.merge(d.checkRequired(t, src)) - if len(errors) > 0 { - return errors + multiError.merge(d.checkRequired(t, src)) + if len(multiError) > 0 { + return multiError } return nil } diff --git a/internal/schema/doc.go b/internal/schema/doc.go index aae9f33f9d..fff0fe7616 100644 --- a/internal/schema/doc.go +++ b/internal/schema/doc.go @@ -60,14 +60,14 @@ certain fields, use a dash for the name and it will be ignored: The supported field types in the destination struct are: - * bool - * float variants (float32, float64) - * int variants (int, int8, int16, int32, int64) - * string - * uint variants (uint, uint8, uint16, uint32, uint64) - * struct - * a pointer to one of the above types - * a slice or a pointer to a slice of one of the above types + - bool + - float variants (float32, float64) + - int variants (int, int8, int16, int32, int64) + - string + - uint variants (uint, uint8, uint16, uint32, uint64) + - struct + - a pointer to one of the above types + - a slice or a pointer to a slice of one of the above types Non-supported types are simply ignored, however custom types can be registered to be converted. diff --git a/internal/storage/memory/config.go b/internal/storage/memory/config.go new file mode 100644 index 0000000000..07d13edb5b --- /dev/null +++ b/internal/storage/memory/config.go @@ -0,0 +1,33 @@ +package memory + +import "time" + +// Config defines the config for storage. +type Config struct { + // Time before deleting expired keys + // + // Default is 10 * time.Second + GCInterval time.Duration +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + GCInterval: 10 * time.Second, +} + +// configDefault is a helper function to set default values +func configDefault(config ...Config) Config { + // Return default config if nothing provided + if len(config) < 1 { + return ConfigDefault + } + + // Override default config + cfg := config[0] + + // Set default values + if int(cfg.GCInterval.Seconds()) <= 0 { + cfg.GCInterval = ConfigDefault.GCInterval + } + return cfg +} diff --git a/internal/storage/memory/memory.go b/internal/storage/memory/memory.go index fcf4fc1a82..65ad3389e7 100644 --- a/internal/storage/memory/memory.go +++ b/internal/storage/memory/memory.go @@ -1,8 +1,13 @@ +// Package memory Is a copy of the storage memory from the external storage packet as a purpose to test the behavior +// in the unittests when using a storages from these packets package memory import ( "sync" + "sync/atomic" "time" + + "github.com/gofiber/utils" ) // Storage interface that is implemented by storage providers @@ -14,27 +19,25 @@ type Storage struct { } type entry struct { + data []byte // max value is 4294967295 -> Sun Feb 07 2106 06:28:15 GMT+0000 expiry uint32 - data []byte } // New creates a new memory storage -func New(interval ...time.Duration) *Storage { - // Custom gc interval - var gcInterval time.Duration = 10 - if len(interval) > 1 { - gcInterval = interval[0] - } +func New(config ...Config) *Storage { + // Set default config + cfg := configDefault(config...) // Create storage store := &Storage{ db: make(map[string]entry), - gcInterval: gcInterval * time.Second, + gcInterval: cfg.GCInterval, done: make(chan struct{}), } // Start garbage collector + utils.StartTimeStampUpdater() go store.gc() return store @@ -48,7 +51,7 @@ func (s *Storage) Get(key string) ([]byte, error) { s.mux.RLock() v, ok := s.db[key] s.mux.RUnlock() - if !ok || v.expiry != 0 && v.expiry <= uint32(time.Now().Unix()) { + if !ok || v.expiry != 0 && v.expiry <= atomic.LoadUint32(&utils.Timestamp) { return nil, nil } @@ -64,11 +67,11 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error { var expire uint32 if exp != 0 { - expire = uint32(time.Now().Add(exp).Unix()) + expire = uint32(exp.Seconds()) + atomic.LoadUint32(&utils.Timestamp) } s.mux.Lock() - s.db[key] = entry{expire, val} + s.db[key] = entry{val, expire} s.mux.Unlock() return nil } @@ -102,20 +105,31 @@ func (s *Storage) Close() error { func (s *Storage) gc() { ticker := time.NewTicker(s.gcInterval) defer ticker.Stop() + var expired []string for { select { case <-s.done: return - case t := <-ticker.C: - now := uint32(t.Unix()) - s.mux.Lock() + case <-ticker.C: + expired = expired[:0] + s.mux.RLock() for id, v := range s.db { - if v.expiry != 0 && v.expiry < now { - delete(s.db, id) + if v.expiry != 0 && v.expiry < atomic.LoadUint32(&utils.Timestamp) { + expired = append(expired, id) } } + s.mux.RUnlock() + s.mux.Lock() + for i := range expired { + delete(s.db, expired[i]) + } s.mux.Unlock() } } } + +// Return database client +func (s *Storage) Conn() map[string]entry { + return s.db +} diff --git a/internal/storage/memory/memory_test.go b/internal/storage/memory/memory_test.go new file mode 100644 index 0000000000..f73ac5fa50 --- /dev/null +++ b/internal/storage/memory/memory_test.go @@ -0,0 +1,154 @@ +package memory + +import ( + "testing" + "time" + + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" +) + +var testStore = New() + +func Test_Storage_Memory_Set(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) +} + +func Test_Storage_Memory_Set_Override(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + err = testStore.Set(key, val, 0) + require.NoError(t, err) +} + +func Test_Storage_Memory_Get(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, val, result) +} + +func Test_Storage_Memory_Set_Expiration(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + exp = 1 * time.Second + ) + + err := testStore.Set(key, val, exp) + require.NoError(t, err) + + time.Sleep(1100 * time.Millisecond) +} + +func Test_Storage_Memory_Get_Expired(t *testing.T) { + var ( + key = "john" + ) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, true, len(result) == 0) +} + +func Test_Storage_Memory_Get_NotExist(t *testing.T) { + + result, err := testStore.Get("notexist") + require.NoError(t, err) + require.Equal(t, true, len(result) == 0) +} + +func Test_Storage_Memory_Delete(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + err = testStore.Delete(key) + require.NoError(t, err) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, true, len(result) == 0) +} + +func Test_Storage_Memory_Reset(t *testing.T) { + var ( + val = []byte("doe") + ) + + err := testStore.Set("john1", val, 0) + require.NoError(t, err) + + err = testStore.Set("john2", val, 0) + require.NoError(t, err) + + err = testStore.Reset() + require.NoError(t, err) + + result, err := testStore.Get("john1") + require.NoError(t, err) + require.Equal(t, true, len(result) == 0) + + result, err = testStore.Get("john2") + require.NoError(t, err) + require.Equal(t, true, len(result) == 0) +} + +func Test_Storage_Memory_Close(t *testing.T) { + require.NoError(t, testStore.Close()) +} + +func Test_Storage_Memory_Conn(t *testing.T) { + require.True(t, testStore.Conn() != nil) +} + +// go test -v -run=^$ -bench=Benchmark_Storage_Memory -benchmem -count=4 +func Benchmark_Storage_Memory(b *testing.B) { + keyLength := 1000 + keys := make([]string, keyLength) + for i := 0; i < keyLength; i++ { + keys[i] = utils.UUID() + } + value := []byte("joe") + + ttl := 2 * time.Second + b.Run("fiber_memory", func(b *testing.B) { + d := New() + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + for _, key := range keys { + d.Set(key, value, ttl) + } + for _, key := range keys { + _, _ = d.Get(key) + } + for _, key := range keys { + d.Delete(key) + } + } + }) +} diff --git a/listen.go b/listen.go new file mode 100644 index 0000000000..1ecaa87318 --- /dev/null +++ b/listen.go @@ -0,0 +1,434 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "log" + "net" + "os" + "path/filepath" + "reflect" + "runtime" + "sort" + "strconv" + "strings" + "text/tabwriter" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// Figlet text to show Fiber ASCII art on startup message +var figletFiberText = ` + _______ __ + / ____(_) /_ ___ _____ + / /_ / / __ \/ _ \/ ___/ + / __/ / / /_/ / __/ / +/_/ /_/_.___/\___/_/ %s` + +// ListenConfig is a struct to customize startup of Fiber. +// +// TODO: Add timeout for graceful shutdown. +type ListenConfig struct { + // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) + // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose. + // + // Default: NetworkTCP4 + ListenerNetwork string `json:"listener_network"` + + // CertFile is a path of certficate file. + // If you want to use TLS, you have to enter this field. + // + // Default : "" + CertFile string `json:"cert_file"` + + // KeyFile is a path of certficate's private key. + // If you want to use TLS, you have to enter this field. + // + // Default : "" + CertKeyFile string `json:"cert_key_file"` + + // CertClientFile is a path of client certficate. + // If you want to use mTLS, you have to enter this field. + // + // Default : "" + CertClientFile string `json:"cert_client_file"` + + // GracefulContext is a field to shutdown Fiber by given context gracefully. + // + // Default: nil + GracefulContext context.Context `json:"graceful_context"` + + // TLSConfigFunc allows customizing tls.Config as you want. + // + // Default: nil + TLSConfigFunc func(tlsConfig *tls.Config) `json:"tls_config_func"` + + // ListenerFunc allows accessing and customizing net.Listener. + // + // Default: nil + ListenerAddrFunc func(addr net.Addr) `json:"listener_addr_func"` + + // BeforeServeFunc allows customizing and accessing fiber app before serving the app. + // + // Default: nil + BeforeServeFunc func(app *App) error `json:"before_serve_func"` + + // When set to true, it will not print out the «Fiber» ASCII art and listening address. + // + // Default: false + DisableStartupMessage bool `json:"disable_startup_message"` + + // When set to true, this will spawn multiple Go processes listening on the same port. + // + // Default: false + EnablePrefork bool `json:"enable_prefork"` + + // If set to true, will print all routes with their method, path and handler. + // + // Default: false + EnablePrintRoutes bool `json:"enable_print_routes"` + + // OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal. + // + // Default: Print error with log.Fatalf() + OnShutdownError func(err error) + + // OnShutdownSuccess allows to customize success behavior when to graceful shutdown server by given signal. + // + // Default: nil + OnShutdownSuccess func() +} + +// listenConfigDefault is a function to set default values of ListenConfig. +func listenConfigDefault(config ...ListenConfig) ListenConfig { + if len(config) < 1 { + return ListenConfig{ + ListenerNetwork: NetworkTCP4, + OnShutdownError: func(err error) { + log.Fatalf("shutdown: %v", err) + }, + } + } + + cfg := config[0] + if cfg.ListenerNetwork == "" { + cfg.ListenerNetwork = NetworkTCP4 + } + + if cfg.OnShutdownError == nil { + cfg.OnShutdownError = func(err error) { + log.Fatalf("shutdown: %v", err) + } + } + + return cfg +} + +// Listen serves HTTP requests from the given addr. +// You should enter custom ListenConfig to customize startup. (TLS, mTLS, prefork...) +// +// app.Listen(":8080") +// app.Listen("127.0.0.1:8080") +// app.Listen(":8080", ListenConfig{EnablePrefork: true}) +func (app *App) Listen(addr string, config ...ListenConfig) error { + cfg := listenConfigDefault(config...) + + // Configure TLS + var tlsConfig *tls.Config = nil + if cfg.CertFile != "" && cfg.CertKeyFile != "" { + cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.CertKeyFile) + if err != nil { + return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", cfg.CertFile, cfg.CertKeyFile, err) + } + + tlsHandler := &TLSHandler{} + tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{ + cert, + }, + GetCertificate: tlsHandler.GetClientInfo, + } + + if cfg.CertClientFile != "" { + clientCACert, err := os.ReadFile(filepath.Clean(cfg.CertClientFile)) + if err != nil { + return err + } + + clientCertPool := x509.NewCertPool() + clientCertPool.AppendCertsFromPEM(clientCACert) + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = clientCertPool + } + + // Attach the tlsHandler to the config + app.SetTLSHandler(tlsHandler) + } + + if cfg.TLSConfigFunc != nil { + cfg.TLSConfigFunc(tlsConfig) + } + + // Graceful shutdown + if cfg.GracefulContext != nil { + ctx, cancel := context.WithCancel(cfg.GracefulContext) + defer cancel() + + go app.gracefulShutdown(ctx, cfg) + } + + // Start prefork + if cfg.EnablePrefork { + return app.prefork(addr, tlsConfig, cfg) + } + + // Configure Listener + ln, err := app.createListener(addr, tlsConfig, cfg) + if err != nil { + return err + } + + // prepare the server for the start + app.startupProcess() + + // Print startup message & routes + app.printMessages(cfg, ln) + + // Serve + if cfg.BeforeServeFunc != nil { + if err := cfg.BeforeServeFunc(app); err != nil { + return err + } + } + + return app.server.Serve(ln) +} + +// Listener serves HTTP requests from the given listener. +// You should enter custom ListenConfig to customize startup. (prefork, startup message, graceful shutdown...) +func (app *App) Listener(ln net.Listener, config ...ListenConfig) error { + cfg := listenConfigDefault(config...) + + // Graceful shutdown + if cfg.GracefulContext != nil { + ctx, cancel := context.WithCancel(cfg.GracefulContext) + defer cancel() + + go app.gracefulShutdown(ctx, cfg) + } + + // prepare the server for the start + app.startupProcess() + + // Print startup message & routes + app.printMessages(cfg, ln) + + // Serve + if cfg.BeforeServeFunc != nil { + if err := cfg.BeforeServeFunc(app); err != nil { + return err + } + } + + // Prefork is not supported for custom listeners + if cfg.EnablePrefork { + fmt.Println("[Warning] Prefork isn't supported for custom listeners.") + } + + return app.server.Serve(ln) +} + +// Create listener function. +func (app *App) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) (net.Listener, error) { + var listener net.Listener + var err error + + if tlsConfig != nil { + listener, err = tls.Listen(cfg.ListenerNetwork, addr, tlsConfig) + } else { + listener, err = net.Listen(cfg.ListenerNetwork, addr) + } + + if cfg.ListenerAddrFunc != nil { + cfg.ListenerAddrFunc(listener.Addr()) + } + + return listener, err +} + +func (app *App) printMessages(cfg ListenConfig, ln net.Listener) { + // Print startup message + if !cfg.DisableStartupMessage { + app.startupMessage(ln.Addr().String(), getTlsConfig(ln) != nil, "", cfg) + } + + // Print routes + if cfg.EnablePrintRoutes { + app.printRoutesMessage() + } +} + +// startupMessage prepares the startup message with the handler number, port, address and other information +func (app *App) startupMessage(addr string, tls bool, pids string, cfg ListenConfig) { + // ignore child processes + if IsChild() { + return + } + + // Alias colors + colors := app.config.ColorScheme + + host, port := parseAddr(addr) + if host == "" { + if cfg.ListenerNetwork == NetworkTCP6 { + host = "[::1]" + } else { + host = "0.0.0.0" + } + } + + scheme := "http" + if tls { + scheme = "https" + } + + isPrefork := "Disabled" + if cfg.EnablePrefork { + isPrefork = "Enabled" + } + + procs := strconv.Itoa(runtime.GOMAXPROCS(0)) + if !cfg.EnablePrefork { + procs = "1" + } + + out := colorable.NewColorableStdout() + if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { + out = colorable.NewNonColorable(os.Stdout) + } + + _, _ = fmt.Fprintf(out, "%s\n", fmt.Sprintf(figletFiberText, colors.Red+"v"+Version+colors.Reset)) + _, _ = fmt.Fprintf(out, strings.Repeat("-", 50)+"\n") + + if host == "0.0.0.0" { + _, _ = fmt.Fprintf(out, + "%sINFO%s Server started on %s%s://127.0.0.1:%s%s (bound on host 0.0.0.0 and port %s)\n", + colors.Green, colors.Reset, colors.Blue, scheme, port, colors.Reset, port) + } else { + _, _ = fmt.Fprintf(out, + "%sINFO%s Server started on %s%s%s\n", + colors.Green, colors.Reset, colors.Blue, fmt.Sprintf("%s://%s:%s", scheme, host, port), colors.Reset) + } + + if app.config.AppName != "" { + _, _ = fmt.Fprintf(out, "%sINFO%s Application name: %s%s%s\n", colors.Green, colors.Reset, colors.Blue, app.config.AppName, colors.Reset) + } + _, _ = fmt.Fprintf(out, + "%sINFO%s Total handlers count: %s%s%s\n", + colors.Green, colors.Reset, colors.Blue, strconv.Itoa(int(app.handlersCount)), colors.Reset) + if isPrefork == "Enabled" { + _, _ = fmt.Fprintf(out, "%sINFO%s Prefork: %s%s%s\n", colors.Green, colors.Reset, colors.Blue, isPrefork, colors.Reset) + } else { + _, _ = fmt.Fprintf(out, "%sINFO%s Prefork: %s%s%s\n", colors.Green, colors.Reset, colors.Red, isPrefork, colors.Reset) + } + _, _ = fmt.Fprintf(out, "%sINFO%s PID: %s%v%s\n", colors.Green, colors.Reset, colors.Blue, os.Getpid(), colors.Reset) + _, _ = fmt.Fprintf(out, "%sINFO%s Total process count: %s%s%s\n", colors.Green, colors.Reset, colors.Blue, procs, colors.Reset) + + if cfg.EnablePrefork { + // Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs + var pidSlice []string + for _, v := range strings.Split(pids, ",") { + if v != "" { + pidSlice = append(pidSlice, v) + } + } + + _, _ = fmt.Fprintf(out, "%sINFO%s Child PIDs: %s", colors.Green, colors.Reset, colors.Blue) + totalPids := len(pidSlice) + rowTotalPidCount := 10 + for i := 0; i < totalPids; i += rowTotalPidCount { + start := i + end := i + rowTotalPidCount + if end > totalPids { + end = totalPids + } + for n, pid := range pidSlice[start:end] { + _, _ = fmt.Fprintf(out, "%s", pid) + if n+1 != len(pidSlice[start:end]) { + _, _ = fmt.Fprintf(out, ", ") + } + } + _, _ = fmt.Fprintf(out, "\n%s", colors.Reset) + } + } +} + +// printRoutesMessage print all routes with method, path, name and handlers +// in a format of table, like this: +// method | path | name | handlers +// GET | / | routeName | github.com/gofiber/fiber/v3.emptyHandler +// HEAD | / | | github.com/gofiber/fiber/v3.emptyHandler +func (app *App) printRoutesMessage() { + // ignore child processes + if IsChild() { + return + } + + // Alias colors + colors := app.config.ColorScheme + + var routes []RouteMessage + for _, routeStack := range app.stack { + for _, route := range routeStack { + var newRoute = RouteMessage{} + newRoute.name = route.Name + newRoute.method = route.Method + newRoute.path = route.Path + for _, handler := range route.Handlers { + newRoute.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " " + } + routes = append(routes, newRoute) + } + } + + out := colorable.NewColorableStdout() + if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { + out = colorable.NewNonColorable(os.Stdout) + } + + w := tabwriter.NewWriter(out, 1, 1, 1, ' ', 0) + // Sort routes by path + sort.Slice(routes, func(i, j int) bool { + return routes[i].path < routes[j].path + }) + + _, _ = fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow) + _, _ = fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow) + for _, route := range routes { + _, _ = fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %s%s\n", colors.Blue, route.method, colors.White, colors.Green, route.path, colors.White, colors.Cyan, route.name, colors.White, colors.Yellow, route.handlers) + } + + _ = w.Flush() +} + +// shutdown goroutine +func (app *App) gracefulShutdown(ctx context.Context, cfg ListenConfig) { + <-ctx.Done() + + if err := app.Shutdown(); err != nil { + cfg.OnShutdownError(err) + } + + if success := cfg.OnShutdownSuccess; success != nil { + success() + } +} diff --git a/listen_test.go b/listen_test.go new file mode 100644 index 0000000000..de482e4331 --- /dev/null +++ b/listen_test.go @@ -0,0 +1,487 @@ +package fiber + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "log" + "net" + "os" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp/fasthttputil" +) + +// go test -run Test_Listen +func Test_Listen(t *testing.T) { + app := New() + + require.False(t, app.Listen(":99999") == nil) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":4003", ListenConfig{DisableStartupMessage: true})) +} + +// go test -run Test_Listen_Graceful_Shutdown +func Test_Listen_Graceful_Shutdown(t *testing.T) { + var mu sync.Mutex + var shutdown bool + + app := New() + + app.Get("/", func(c Ctx) error { + return c.SendString(c.Hostname()) + }) + + ln := fasthttputil.NewInmemoryListener() + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond) + defer cancel() + + err := app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + GracefulContext: ctx, + OnShutdownSuccess: func() { + mu.Lock() + shutdown = true + mu.Unlock() + }, + }) + + require.NoError(t, err) + }() + + testCases := []struct { + Time time.Duration + ExpectedBody string + ExpectedStatusCode int + ExceptedErrsLen int + }{ + {Time: 100 * time.Millisecond, ExpectedBody: "example.com", ExpectedStatusCode: StatusOK, ExceptedErrsLen: 0}, + {Time: 500 * time.Millisecond, ExpectedBody: "", ExpectedStatusCode: 0, ExceptedErrsLen: 1}, + } + + for _, tc := range testCases { + time.Sleep(tc.Time) + + a := Get("http://example.com") + a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } + code, body, errs := a.String() + + require.Equal(t, tc.ExpectedStatusCode, code) + require.Equal(t, tc.ExpectedBody, body) + require.Equal(t, tc.ExceptedErrsLen, len(errs)) + } + + mu.Lock() + require.True(t, shutdown) + mu.Unlock() +} + +// go test -run Test_Listen_Prefork +func Test_Listen_Prefork(t *testing.T) { + testPreforkMaster = true + + app := New() + + require.Nil(t, app.Listen(":99999", ListenConfig{DisableStartupMessage: true, EnablePrefork: true})) +} + +// go test -run Test_Listen_TLS +func Test_Listen_TLS(t *testing.T) { + app := New() + + // invalid port + require.False(t, app.Listen(":99999", ListenConfig{ + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + }) == nil) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + })) + +} + +// go test -run Test_Listen_TLS_Prefork +func Test_Listen_TLS_Prefork(t *testing.T) { + testPreforkMaster = true + + app := New() + + // invalid key file content + require.False(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/template.tmpl", + }) == nil) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":99999", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + })) + +} + +// go test -run Test_Listen_MutualTLS +func Test_Listen_MutualTLS(t *testing.T) { + app := New() + + // invalid port + require.False(t, app.Listen(":99999", ListenConfig{ + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + CertClientFile: "./.github/testdata/ca-chain.cert.pem", + }) == nil) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + CertClientFile: "./.github/testdata/ca-chain.cert.pem", + })) + +} + +// go test -run Test_Listen_MutualTLS_Prefork +func Test_Listen_MutualTLS_Prefork(t *testing.T) { + testPreforkMaster = true + + app := New() + + // invalid key file content + require.False(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/template.html", + CertClientFile: "./.github/testdata/ca-chain.cert.pem", + }) == nil) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":99999", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + CertClientFile: "./.github/testdata/ca-chain.cert.pem", + })) + +} + +// go test -run Test_Listener +func Test_Listener(t *testing.T) { + app := New() + + go func() { + time.Sleep(500 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + ln := fasthttputil.NewInmemoryListener() + require.Nil(t, app.Listener(ln)) +} + +func Test_App_Listener_TLS_Listener(t *testing.T) { + // Create tls certificate + cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") + if err != nil { + require.NoError(t, err) + } + config := &tls.Config{Certificates: []tls.Certificate{cer}} + + ln, err := tls.Listen(NetworkTCP4, ":0", config) + require.NoError(t, err) + + app := New() + + go func() { + time.Sleep(time.Millisecond * 500) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listener(ln)) +} + +// go test -run Test_Listen_TLSConfigFunc +func Test_Listen_TLSConfigFunc(t *testing.T) { + var callTLSConfig bool + app := New() + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + TLSConfigFunc: func(tlsConfig *tls.Config) { + callTLSConfig = true + }, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + })) + + require.True(t, callTLSConfig) +} + +// go test -run Test_Listen_ListenerAddrFunc +func Test_Listen_ListenerAddrFunc(t *testing.T) { + var network string + app := New() + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + ListenerAddrFunc: func(addr net.Addr) { + network = addr.Network() + }, + CertFile: "./.github/testdata/ssl.pem", + CertKeyFile: "./.github/testdata/ssl.key", + })) + + require.Equal(t, "tcp", network) +} + +// go test -run Test_Listen_BeforeServeFunc +func Test_Listen_BeforeServeFunc(t *testing.T) { + var handlers uint32 + app := New() + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Equal(t, errors.New("test"), app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + BeforeServeFunc: func(fiber *App) error { + handlers = fiber.HandlersCount() + + return errors.New("test") + }, + })) + + require.Equal(t, uint32(0), handlers) +} + +// go test -run Test_Listen_ListenerNetwork +func Test_Listen_ListenerNetwork(t *testing.T) { + var network string + app := New() + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + ListenerNetwork: NetworkTCP6, + ListenerAddrFunc: func(addr net.Addr) { + network = addr.String() + }, + })) + + require.True(t, strings.Contains(network, "[::]:")) + + go func() { + time.Sleep(1000 * time.Millisecond) + require.Nil(t, app.Shutdown()) + }() + + require.Nil(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + ListenerNetwork: NetworkTCP4, + ListenerAddrFunc: func(addr net.Addr) { + network = addr.String() + }, + })) + + require.True(t, strings.Contains(network, "0.0.0.0:")) +} + +// go test -run Test_Listen_Master_Process_Show_Startup_Message +func Test_Listen_Master_Process_Show_Startup_Message(t *testing.T) { + cfg := ListenConfig{ + EnablePrefork: true, + } + + startupMessage := captureOutput(func() { + New(). + startupMessage(":3000", true, strings.Repeat(",11111,22222,33333,44444,55555,60000", 10), cfg) + }) + colors := Colors{} + fmt.Println(startupMessage) + require.True(t, strings.Contains(startupMessage, "https://127.0.0.1:3000")) + require.True(t, strings.Contains(startupMessage, "(bound on host 0.0.0.0 and port 3000)")) + require.True(t, strings.Contains(startupMessage, "Child PIDs")) + require.True(t, strings.Contains(startupMessage, "11111, 22222, 33333, 44444, 55555, 60000")) + require.True(t, strings.Contains(startupMessage, fmt.Sprintf("Prefork: %sEnabled%s", colors.Blue, colors.Reset))) +} + +// go test -run Test_Listen_Master_Process_Show_Startup_MessageWithAppName +func Test_Listen_Master_Process_Show_Startup_MessageWithAppName(t *testing.T) { + cfg := ListenConfig{ + EnablePrefork: true, + } + + app := New(Config{AppName: "Test App v3.0.0"}) + startupMessage := captureOutput(func() { + app.startupMessage(":3000", true, strings.Repeat(",11111,22222,33333,44444,55555,60000", 10), cfg) + }) + fmt.Println(startupMessage) + require.Equal(t, "Test App v3.0.0", app.Config().AppName) + require.True(t, strings.Contains(startupMessage, app.Config().AppName)) +} + +// go test -run Test_Listen_Master_Process_Show_Startup_MessageWithAppNameNonAscii +func Test_Listen_Master_Process_Show_Startup_MessageWithAppNameNonAscii(t *testing.T) { + cfg := ListenConfig{ + EnablePrefork: true, + } + + appName := "Serveur de vérification des données" + app := New(Config{AppName: appName}) + + startupMessage := captureOutput(func() { + app.startupMessage(":3000", false, "", cfg) + }) + fmt.Println(startupMessage) + require.True(t, strings.Contains(startupMessage, "Serveur de vérification des données")) +} + +// go test -run Test_Listen_Master_Process_Show_Startup_MessageWithDisabledPreforkAndCustomEndpoint +func Test_Listen_Master_Process_Show_Startup_MessageWithDisabledPreforkAndCustomEndpoint(t *testing.T) { + cfg := ListenConfig{ + EnablePrefork: false, + } + + appName := "Fiber Example Application" + app := New(Config{AppName: appName}) + startupMessage := captureOutput(func() { + app.startupMessage("server.com:8081", true, strings.Repeat(",11111,22222,33333,44444,55555,60000", 5), cfg) + }) + colors := Colors{} + fmt.Println(startupMessage) + require.True(t, strings.Contains(startupMessage, fmt.Sprintf("%sINFO%s", colors.Green, colors.Reset))) + require.True(t, strings.Contains(startupMessage, fmt.Sprintf("%s%s%s", colors.Blue, appName, colors.Reset))) + require.True(t, strings.Contains(startupMessage, fmt.Sprintf("%s%s%s", colors.Blue, "https://server.com:8081", colors.Reset))) + require.True(t, strings.Contains(startupMessage, fmt.Sprintf("Prefork: %sDisabled%s", colors.Red, colors.Reset))) +} + +// go test -run Test_Listen_Print_Route +func Test_Listen_Print_Route(t *testing.T) { + app := New() + app.Get("/", emptyHandler).Name("routeName") + printRoutesMessage := captureOutput(func() { + app.printRoutesMessage() + }) + fmt.Println(printRoutesMessage) + require.True(t, strings.Contains(printRoutesMessage, "GET")) + require.True(t, strings.Contains(printRoutesMessage, "/")) + require.True(t, strings.Contains(printRoutesMessage, "emptyHandler")) + require.True(t, strings.Contains(printRoutesMessage, "routeName")) +} + +// go test -run Test_Listen_Print_Route_With_Group +func Test_Listen_Print_Route_With_Group(t *testing.T) { + app := New() + app.Get("/", emptyHandler) + + v1 := app.Group("v1") + v1.Get("/test", emptyHandler).Name("v1") + v1.Post("/test/fiber", emptyHandler) + v1.Put("/test/fiber/*", emptyHandler) + + printRoutesMessage := captureOutput(func() { + app.printRoutesMessage() + }) + + require.True(t, strings.Contains(printRoutesMessage, "GET")) + require.True(t, strings.Contains(printRoutesMessage, "/")) + require.True(t, strings.Contains(printRoutesMessage, "emptyHandler")) + require.True(t, strings.Contains(printRoutesMessage, "/v1/test")) + require.True(t, strings.Contains(printRoutesMessage, "POST")) + require.True(t, strings.Contains(printRoutesMessage, "/v1/test/fiber")) + require.True(t, strings.Contains(printRoutesMessage, "PUT")) + require.True(t, strings.Contains(printRoutesMessage, "/v1/test/fiber/*")) +} + +func captureOutput(f func()) string { + reader, writer, err := os.Pipe() + if err != nil { + panic(err) + } + stdout := os.Stdout + stderr := os.Stderr + defer func() { + os.Stdout = stdout + os.Stderr = stderr + log.SetOutput(os.Stderr) + }() + os.Stdout = writer + os.Stderr = writer + log.SetOutput(writer) + out := make(chan string) + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + var buf bytes.Buffer + wg.Done() + _, err := io.Copy(&buf, reader) + if err != nil { + panic(err) + } + out <- buf.String() + }() + wg.Wait() + f() + err = writer.Close() + if err != nil { + panic(err) + } + return <-out +} + +func emptyHandler(_ Ctx) error { + return nil +} diff --git a/middleware/adaptor/README.md b/middleware/adaptor/README.md index a2c543c053..abc0184a71 100644 --- a/middleware/adaptor/README.md +++ b/middleware/adaptor/README.md @@ -10,7 +10,7 @@ Converter for net/http handlers to/from Fiber request handlers, special thanks t ### Install ``` -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/adaptor/v2 ``` @@ -33,7 +33,7 @@ import ( "net/http" "github.com/gofiber/adaptor/v2" - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" ) func main() { @@ -68,7 +68,7 @@ import ( "net/http" "github.com/gofiber/adaptor/v2" - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" ) func main() { @@ -98,7 +98,7 @@ import ( "net/http" "github.com/gofiber/adaptor/v2" - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" ) func main() { @@ -123,7 +123,7 @@ package main import ( "github.com/gofiber/adaptor/v2" - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "net/http" ) func main() { diff --git a/middleware/adaptor/adopter.go b/middleware/adaptor/adopter.go index efc30cca56..0f37122cdd 100644 --- a/middleware/adaptor/adopter.go +++ b/middleware/adaptor/adopter.go @@ -5,12 +5,12 @@ package adaptor import ( - "io/ioutil" + "io" "net" "net/http" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpadaptor" ) @@ -75,7 +75,7 @@ func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc { defer fasthttp.ReleaseRequest(req) // Convert net/http -> fasthttp request if r.Body != nil { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError) return diff --git a/middleware/adaptor/adopter_test.go b/middleware/adaptor/adopter_test.go index 8cd0af9dd6..8e9531e7b1 100644 --- a/middleware/adaptor/adopter_test.go +++ b/middleware/adaptor/adopter_test.go @@ -7,7 +7,6 @@ package adaptor import ( "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -70,7 +69,7 @@ func Test_HTTPHandler(t *testing.T) { if r.RemoteAddr != expectedRemoteAddr { t.Fatalf("unexpected remoteAddr %q. Expecting %q", r.RemoteAddr, expectedRemoteAddr) } - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) r.Body.Close() if err != nil { t.Fatalf("unexpected error when reading request body: %s", err) @@ -246,8 +245,8 @@ func testFiberToHandlerFunc(t *testing.T, checkDefaultPort bool, app ...*fiber.A if contentLength != expectedContentLength { t.Fatalf("unexpected contentLength %d. Expecting %d", contentLength, expectedContentLength) } - if c.Hostname() != expectedHost { - t.Fatalf("unexpected host %q. Expecting %q", c.Hostname(), expectedHost) + if c.Host() != expectedHost { + t.Fatalf("unexpected host %q. Expecting %q", c.Host(), expectedHost) } remoteAddr := c.Context().RemoteAddr().String() if remoteAddr != expectedRemoteAddr { @@ -319,7 +318,7 @@ func testFiberToHandlerFunc(t *testing.T, checkDefaultPort bool, app ...*fiber.A } } -func setFiberContextValueMiddleware(next fiber.Handler, key string, value interface{}) fiber.Handler { +func setFiberContextValueMiddleware(next fiber.Handler, key string, value any) fiber.Handler { return func(c fiber.Ctx) error { c.Locals(key, value) return next(c) diff --git a/middleware/basicauth/README.md b/middleware/basicauth/README.md index 588870c66c..ecebee9bf8 100644 --- a/middleware/basicauth/README.md +++ b/middleware/basicauth/README.md @@ -24,8 +24,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/basicauth" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/basicauth" ) ``` diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index 67a280a92c..d3349d8635 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // New creates a new middleware handler diff --git a/middleware/basicauth/basicauth_test.go b/middleware/basicauth/basicauth_test.go index 7e89d0e8ef..7656c9eb6e 100644 --- a/middleware/basicauth/basicauth_test.go +++ b/middleware/basicauth/basicauth_test.go @@ -9,7 +9,7 @@ import ( b64 "encoding/base64" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -25,8 +25,8 @@ func Test_BasicAuth_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_Middleware_BasicAuth(t *testing.T) { @@ -80,15 +80,15 @@ func Test_Middleware_BasicAuth(t *testing.T) { req := httptest.NewRequest("GET", "/testauth", nil) req.Header.Add("Authorization", "Basic "+creds) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, tt.statusCode, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, tt.statusCode, resp.StatusCode) if tt.statusCode == 200 { - utils.AssertEqual(t, fmt.Sprintf("%s%s", tt.username, tt.password), string(body)) + require.Equal(t, fmt.Sprintf("%s%s", tt.username, tt.password), string(body)) } } } @@ -120,5 +120,5 @@ func Benchmark_Middleware_BasicAuth(b *testing.B) { h(fctx) } - utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) } diff --git a/middleware/basicauth/config.go b/middleware/basicauth/config.go index 9a32b5ce17..6e076a3738 100644 --- a/middleware/basicauth/config.go +++ b/middleware/basicauth/config.go @@ -4,7 +4,7 @@ import ( "crypto/subtle" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // Config defines the config for middleware. diff --git a/middleware/cache/README.md b/middleware/cache/README.md index 35737539a8..b30b452878 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -2,6 +2,10 @@ Cache middleware for [Fiber](https://github.com/gofiber/fiber) designed to intercept responses and cache them. This middleware will cache the `Body`, `Content-Type` and `StatusCode` using the `c.Path()` (or a string returned by the Key function) as unique identifier. Special thanks to [@codemicro](https://github.com/codemicro/fiber-cache) for creating this middleware for Fiber core! +Request Directives
+`Cache-Control: no-cache` will return the up-to-date response but still caches it. You will always get a `miss` cache status.
+`Cache-Control: no-store` will refrain from caching. You will always get the up-to-date response. + ## Table of Contents - [Cache Middleware](#cache-middleware) @@ -26,8 +30,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cache" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cache" ) ``` @@ -102,12 +106,12 @@ type Config struct { // Default: func(c fiber.Ctx) string { // return utils.CopyString(c.Path()) // } - KeyGenerator func(*fiber.Ctx) string + KeyGenerator func(fiber.Ctx) string // allows you to generate custom Expiration Key By Key, default is Expiration (Optional) // // Default: nil - ExpirationGenerator func(*fiber.Ctx, *Config) time.Duration + ExpirationGenerator func(fiber.Ctx, *Config) time.Duration // Store is used to store the state of the middleware // @@ -125,6 +129,12 @@ type Config struct { // // Default: 0 MaxBytes uint + + // You can specify HTTP methods to cache. + // The middleware just caches the routes of its methods in this slice. + // + // Default: []string{fiber.MethodGet, fiber.MethodHead} + Methods []string } ``` @@ -144,5 +154,6 @@ var ConfigDefault = Config{ StoreResponseHeaders: false, Storage: nil, MaxBytes: 0, + Methods: []string{fiber.MethodGet, fiber.MethodHead}, } ``` diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 7ea8a7c860..b23c8f94b7 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -4,12 +4,13 @@ package cache import ( "strconv" + "strings" "sync" "sync/atomic" "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // timestampUpdatePeriod is the period which is used to check the cache expiration. @@ -27,7 +28,13 @@ const ( cacheMiss = "miss" ) -var ignoreHeaders = map[string]any{ +// directives +const ( + noCache = "no-cache" + noStore = "no-store" +) + +var ignoreHeaders = map[string]interface{}{ "Connection": nil, "Keep-Alive": nil, "Proxy-Authenticate": nil, @@ -83,8 +90,20 @@ func New(config ...Config) fiber.Handler { // Return new handler return func(c fiber.Ctx) error { - // Only cache GET and HEAD methods - if c.Method() != fiber.MethodGet && c.Method() != fiber.MethodHead { + // Refrain from caching + if hasRequestDirective(c, noStore) { + return c.Next() + } + + // Only cache selected methods + var isExists bool + for _, method := range cfg.Methods { + if c.Method() == method { + isExists = true + } + } + + if !isExists { c.Set(cfg.CacheHeader, cacheUnreachable) return c.Next() } @@ -109,7 +128,7 @@ func New(config ...Config) fiber.Handler { _, size := heap.remove(e.heapidx) storedBytes -= size } - } else if e.exp != 0 { + } else if e.exp != 0 && !hasRequestDirective(c, noCache) { // Separate body value to avoid msgp serialization // We can store raw bytes with Storage 👍 if cfg.Storage != nil { @@ -228,3 +247,8 @@ func New(config ...Config) fiber.Handler { return nil } } + +// Check if request has directive +func hasRequestDirective(c fiber.Ctx, directive string) bool { + return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) +} diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 0ab9df5a6c..f2f98c3302 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -16,7 +16,9 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/storage/memory" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/fiber/v3/middleware/etag" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -35,11 +37,11 @@ func Test_Cache_CacheControl(t *testing.T) { }) _, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl)) + require.NoError(t, err) + require.Equal(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl)) } func Test_Cache_Expired(t *testing.T) { @@ -53,17 +55,17 @@ func Test_Cache_Expired(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // Sleep until the cache is expired time.Sleep(3 * time.Second) respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if bytes.Equal(body, bodyCached) { t.Errorf("Cache should have expired: %s, %s", body, bodyCached) @@ -71,9 +73,9 @@ func Test_Cache_Expired(t *testing.T) { // Next response should be also cached respCachedNextRound, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) bodyCachedNextRound, err := io.ReadAll(respCachedNextRound.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if !bytes.Equal(bodyCachedNextRound, bodyCached) { t.Errorf("Cache should not have expired: %s, %s", bodyCached, bodyCachedNextRound) @@ -93,18 +95,171 @@ func Test_Cache(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedReq := httptest.NewRequest("GET", "/", nil) cachedResp, err := app.Test(cachedReq) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedBody, err := io.ReadAll(cachedResp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) - utils.AssertEqual(t, cachedBody, body) + require.Equal(t, cachedBody, body) +} + +// go test -run Test_Cache_WithNoCacheRequestDirective +func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { + t.Parallel() + + app := fiber.New() + app.Use(New()) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString(c.Query("id", "1")) + }) + + // Request id = 1 + req := httptest.NewRequest("GET", "/", nil) + resp, err := app.Test(req) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) + require.Equal(t, []byte("1"), body) + // Response cached, entry id = 1 + + // Request id = 2 without Cache-Control: no-cache + cachedReq := httptest.NewRequest("GET", "/?id=2", nil) + cachedResp, err := app.Test(cachedReq) + defer cachedResp.Body.Close() + cachedBody, _ := io.ReadAll(cachedResp.Body) + require.NoError(t, err) + require.Equal(t, cacheHit, cachedResp.Header.Get("X-Cache")) + require.Equal(t, []byte("1"), cachedBody) + // Response not cached, returns cached response, entry id = 1 + + // Request id = 2 with Cache-Control: no-cache + noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache) + noCacheResp, err := app.Test(noCacheReq) + defer noCacheResp.Body.Close() + noCacheBody, _ := io.ReadAll(noCacheResp.Body) + require.NoError(t, err) + require.Equal(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) + require.Equal(t, []byte("2"), noCacheBody) + // Response cached, returns updated response, entry = 2 + + /* Check Test_Cache_WithETagAndNoCacheRequestDirective */ + // Request id = 2 with Cache-Control: no-cache again + noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache) + noCacheResp1, err := app.Test(noCacheReq1) + defer noCacheResp1.Body.Close() + noCacheBody1, _ := io.ReadAll(noCacheResp1.Body) + require.NoError(t, err) + require.Equal(t, cacheMiss, noCacheResp1.Header.Get("X-Cache")) + require.Equal(t, []byte("2"), noCacheBody1) + // Response cached, returns updated response, entry = 2 + + // Request id = 1 without Cache-Control: no-cache + cachedReq1 := httptest.NewRequest("GET", "/", nil) + cachedResp1, err := app.Test(cachedReq1) + defer cachedResp1.Body.Close() + cachedBody1, _ := io.ReadAll(cachedResp1.Body) + require.NoError(t, err) + require.Equal(t, cacheHit, cachedResp1.Header.Get("X-Cache")) + require.Equal(t, []byte("2"), cachedBody1) + // Response not cached, returns cached response, entry id = 2 +} + +// go test -run Test_Cache_WithETagAndNoCacheRequestDirective +func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { + t.Parallel() + + app := fiber.New() + app.Use( + etag.New(), + New(), + ) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString(c.Query("id", "1")) + }) + + // Request id = 1 + req := httptest.NewRequest("GET", "/", nil) + resp, err := app.Test(req) + require.NoError(t, err) + require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + // Response cached, entry id = 1 + + // If response status 200 + etagToken := resp.Header.Get("Etag") + + // Request id = 2 with ETag but without Cache-Control: no-cache + cachedReq := httptest.NewRequest("GET", "/?id=2", nil) + cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + cachedResp, err := app.Test(cachedReq) + require.NoError(t, err) + require.Equal(t, cacheHit, cachedResp.Header.Get("X-Cache")) + require.Equal(t, fiber.StatusNotModified, cachedResp.StatusCode) + // Response not cached, returns cached response, entry id = 1, status not modified + + // Request id = 2 with ETag and Cache-Control: no-cache + noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache) + noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + noCacheResp, err := app.Test(noCacheReq) + require.NoError(t, err) + require.Equal(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) + require.Equal(t, fiber.StatusOK, noCacheResp.StatusCode) + // Response cached, returns updated response, entry id = 2 + + // If response status 200 + etagToken = noCacheResp.Header.Get("Etag") + + // Request id = 2 with ETag and Cache-Control: no-cache again + noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache) + noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + noCacheResp1, err := app.Test(noCacheReq1) + require.NoError(t, err) + require.Equal(t, cacheMiss, noCacheResp1.Header.Get("X-Cache")) + require.Equal(t, fiber.StatusNotModified, noCacheResp1.StatusCode) + // Response cached, returns updated response, entry id = 2, status not modified + + // Request id = 1 without ETag and Cache-Control: no-cache + cachedReq1 := httptest.NewRequest("GET", "/", nil) + cachedResp1, err := app.Test(cachedReq1) + require.NoError(t, err) + require.Equal(t, cacheHit, cachedResp1.Header.Get("X-Cache")) + require.Equal(t, fiber.StatusOK, cachedResp1.StatusCode) + // Response not cached, returns cached response, entry id = 2 +} + +// go test -run Test_Cache_WithNoStoreRequestDirective +func Test_Cache_WithNoStoreRequestDirective(t *testing.T) { + t.Parallel() + + app := fiber.New() + app.Use(New()) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString(c.Query("id", "1")) + }) + + // Request id = 2 + noStoreReq := httptest.NewRequest("GET", "/?id=2", nil) + noStoreReq.Header.Set(fiber.HeaderCacheControl, noStore) + noStoreResp, err := app.Test(noStoreReq) + defer noStoreResp.Body.Close() + noStoreBody, _ := io.ReadAll(noStoreResp.Body) + require.NoError(t, err) + require.Equal(t, []byte("2"), noStoreBody) + // Response not cached, returns updated response } func Test_Cache_WithSeveralRequests(t *testing.T) { @@ -125,18 +280,21 @@ func Test_Cache_WithSeveralRequests(t *testing.T) { for i := 0; i < 10; i++ { func(id int) { rsp, err := app.Test(httptest.NewRequest(http.MethodGet, fmt.Sprintf("/%d", id), nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) - defer rsp.Body.Close() + defer func(Body io.ReadCloser) { + err := Body.Close() + require.NoError(t, err) + }(rsp.Body) idFromServ, err := io.ReadAll(rsp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) a, err := strconv.Atoi(string(idFromServ)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // SomeTimes,The id is not equal with a - utils.AssertEqual(t, id, a) + require.Equal(t, id, a) }(i) } } @@ -156,21 +314,21 @@ func Test_Cache_Invalid_Expiration(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedReq := httptest.NewRequest("GET", "/", nil) cachedResp, err := app.Test(cachedReq) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedBody, err := io.ReadAll(cachedResp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) - utils.AssertEqual(t, cachedBody, body) + require.Equal(t, cachedBody, body) } -func Test_Cache_Invalid_Method(t *testing.T) { +func Test_Cache_Get(t *testing.T) { t.Parallel() app := fiber.New() @@ -186,28 +344,70 @@ func Test_Cache_Invalid_Method(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("POST", "/?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "123", string(body)) + + resp, err = app.Test(httptest.NewRequest("POST", "/?cache=12345", nil)) + require.NoError(t, err) + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "12345", string(body)) + + resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=123", nil)) + require.NoError(t, err) + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "123", string(body)) + + resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=12345", nil)) + require.NoError(t, err) + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "123", string(body)) +} + +func Test_Cache_Post(t *testing.T) { + t.Parallel() + + app := fiber.New() + + app.Use(New(Config{ + Methods: []string{fiber.MethodPost}, + })) + + app.Post("/", func(c fiber.Ctx) error { + return c.SendString(c.Query("cache")) + }) + + app.Get("/get", func(c fiber.Ctx) error { + return c.SendString(c.Query("cache")) + }) + + resp, err := app.Test(httptest.NewRequest("POST", "/?cache=123", nil)) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(body)) + require.NoError(t, err) + require.Equal(t, "123", string(body)) resp, err = app.Test(httptest.NewRequest("POST", "/?cache=12345", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "12345", string(body)) + require.NoError(t, err) + require.Equal(t, "123", string(body)) resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(body)) + require.NoError(t, err) + require.Equal(t, "123", string(body)) resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=12345", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(body)) + require.NoError(t, err) + require.Equal(t, "12345", string(body)) } func Test_Cache_NothingToCache(t *testing.T) { @@ -222,16 +422,16 @@ func Test_Cache_NothingToCache(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) time.Sleep(500 * time.Millisecond) respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if bytes.Equal(body, bodyCached) { t.Errorf("Cache should have expired: %s, %s", body, bodyCached) @@ -259,23 +459,23 @@ func Test_Cache_CustomNext(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, bytes.Equal(body, bodyCached)) - utils.AssertEqual(t, true, respCached.Header.Get(fiber.HeaderCacheControl) != "") + require.NoError(t, err) + require.True(t, bytes.Equal(body, bodyCached)) + require.True(t, respCached.Header.Get(fiber.HeaderCacheControl) != "") _, err = app.Test(httptest.NewRequest("GET", "/error", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) errRespCached, err := app.Test(httptest.NewRequest("GET", "/error", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, errRespCached.Header.Get(fiber.HeaderCacheControl) == "") + require.NoError(t, err) + require.True(t, errRespCached.Header.Get(fiber.HeaderCacheControl) == "") } func Test_CustomKey(t *testing.T) { @@ -294,8 +494,8 @@ func Test_CustomKey(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) _, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, called) + require.NoError(t, err) + require.True(t, called) } func Test_CustomExpiration(t *testing.T) { @@ -317,20 +517,20 @@ func Test_CustomExpiration(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, called) - utils.AssertEqual(t, 1, newCacheTime) + require.NoError(t, err) + require.True(t, called) + require.Equal(t, 1, newCacheTime) // Sleep until the cache is expired time.Sleep(1 * time.Second) cachedResp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedBody, err := io.ReadAll(cachedResp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if bytes.Equal(body, cachedBody) { t.Errorf("Cache should have expired: %s, %s", body, cachedBody) @@ -338,9 +538,9 @@ func Test_CustomExpiration(t *testing.T) { // Next response should be cached cachedRespNextRound, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedBodyNextRound, err := io.ReadAll(cachedRespNextRound.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if !bytes.Equal(cachedBodyNextRound, cachedBody) { t.Errorf("Cache should not have expired: %s, %s", cachedBodyNextRound, cachedBody) @@ -362,13 +562,13 @@ func Test_AdditionalE2EResponseHeaders(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar")) + require.NoError(t, err) + require.Equal(t, "foobar", resp.Header.Get("X-Foobar")) req = httptest.NewRequest("GET", "/", nil) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar")) + require.NoError(t, err) + require.Equal(t, "foobar", resp.Header.Get("X-Foobar")) } func Test_CacheHeader(t *testing.T) { @@ -396,20 +596,20 @@ func Test_CacheHeader(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) resp, err = app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheHit, resp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheHit, resp.Header.Get("X-Cache")) resp, err = app.Test(httptest.NewRequest("POST", "/?cache=12345", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheUnreachable, resp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheUnreachable, resp.Header.Get("X-Cache")) errRespCached, err := app.Test(httptest.NewRequest("GET", "/error", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheUnreachable, errRespCached.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheUnreachable, errRespCached.Header.Get("X-Cache")) } func Test_Cache_WithHead(t *testing.T) { @@ -418,25 +618,28 @@ func Test_Cache_WithHead(t *testing.T) { app := fiber.New() app.Use(New()) - app.Get("/", func(c fiber.Ctx) error { + handler := func(c fiber.Ctx) error { now := fmt.Sprintf("%d", time.Now().UnixNano()) return c.SendString(now) - }) + } + app.Route("/").Get(handler).Head(handler) req := httptest.NewRequest("HEAD", "/", nil) resp, err := app.Test(req) - utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) cachedReq := httptest.NewRequest("HEAD", "/", nil) cachedResp, err := app.Test(cachedReq) - utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, cacheHit, cachedResp.Header.Get("X-Cache")) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) cachedBody, err := io.ReadAll(cachedResp.Body) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) - utils.AssertEqual(t, cachedBody, body) + require.Equal(t, cachedBody, body) } func Test_Cache_WithHeadThenGet(t *testing.T) { @@ -444,37 +647,39 @@ func Test_Cache_WithHeadThenGet(t *testing.T) { app := fiber.New() app.Use(New()) - app.Get("/", func(c fiber.Ctx) error { + + handler := func(c fiber.Ctx) error { return c.SendString(c.Query("cache")) - }) + } + app.Route("/").Get(handler).Head(handler) headResp, err := app.Test(httptest.NewRequest("HEAD", "/?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) headBody, err := io.ReadAll(headResp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "", string(headBody)) - utils.AssertEqual(t, cacheMiss, headResp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, "", string(headBody)) + require.Equal(t, cacheMiss, headResp.Header.Get("X-Cache")) headResp, err = app.Test(httptest.NewRequest("HEAD", "/?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) headBody, err = io.ReadAll(headResp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "", string(headBody)) - utils.AssertEqual(t, cacheHit, headResp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, "", string(headBody)) + require.Equal(t, cacheHit, headResp.Header.Get("X-Cache")) getResp, err := app.Test(httptest.NewRequest("GET", "/?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) getBody, err := io.ReadAll(getResp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(getBody)) - utils.AssertEqual(t, cacheMiss, getResp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, "123", string(getBody)) + require.Equal(t, cacheMiss, getResp.Header.Get("X-Cache")) getResp, err = app.Test(httptest.NewRequest("GET", "/?cache=123", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) getBody, err = io.ReadAll(getResp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "123", string(getBody)) - utils.AssertEqual(t, cacheHit, getResp.Header.Get("X-Cache")) + require.NoError(t, err) + require.Equal(t, "123", string(getBody)) + require.Equal(t, cacheHit, getResp.Header.Get("X-Cache")) } func Test_CustomCacheHeader(t *testing.T) { @@ -491,8 +696,8 @@ func Test_CustomCacheHeader(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheMiss, resp.Header.Get("Cache-Status")) + require.NoError(t, err) + require.Equal(t, cacheMiss, resp.Header.Get("Cache-Status")) } // Because time points are updated once every X milliseconds, entries in tests can often have @@ -538,8 +743,8 @@ func Test_Cache_MaxBytesOrder(t *testing.T) { for idx, tcase := range cases { rsp, err := app.Test(httptest.NewRequest("GET", tcase[0], nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) + require.NoError(t, err) + require.Equal(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) } } @@ -572,8 +777,8 @@ func Test_Cache_MaxBytesSizes(t *testing.T) { for idx, tcase := range cases { rsp, err := app.Test(httptest.NewRequest("GET", tcase[0], nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) + require.NoError(t, err) + require.Equal(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) } } @@ -601,8 +806,8 @@ func Benchmark_Cache(b *testing.B) { h(fctx) } - utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) - utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000) + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) + require.True(b, len(fctx.Response.Body()) > 30000) } // go test -v -run=^$ -bench=Benchmark_Cache_Storage -benchmem -count=4 @@ -631,8 +836,8 @@ func Benchmark_Cache_Storage(b *testing.B) { h(fctx) } - utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) - utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000) + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) + require.True(b, len(fctx.Response.Body()) > 30000) } func Benchmark_Cache_AdditionalHeaders(b *testing.B) { @@ -659,8 +864,8 @@ func Benchmark_Cache_AdditionalHeaders(b *testing.B) { h(fctx) } - utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) - utils.AssertEqual(b, []byte("foobar"), fctx.Response.Header.Peek("X-Foobar")) + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) + require.Equal(b, []byte("foobar"), fctx.Response.Header.Peek("X-Foobar")) } func Benchmark_Cache_MaxSize(b *testing.B) { @@ -691,7 +896,7 @@ func Benchmark_Cache_MaxSize(b *testing.B) { h(fctx) } - utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) }) } } diff --git a/middleware/cache/config.go b/middleware/cache/config.go index c41bad5087..b18046cff7 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -4,7 +4,7 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // Config defines the config for middleware. @@ -59,6 +59,12 @@ type Config struct { // // Default: 0 MaxBytes uint + + // You can specify HTTP methods to cache. + // The middleware just caches the routes of its methods in this slice. + // + // Default: []string{fiber.MethodGet, fiber.MethodHead} + Methods []string } // ConfigDefault is the default config @@ -74,6 +80,7 @@ var ConfigDefault = Config{ StoreResponseHeaders: false, Storage: nil, MaxBytes: 0, + Methods: []string{fiber.MethodGet, fiber.MethodHead}, } // Helper function to set default values @@ -99,5 +106,8 @@ func configDefault(config ...Config) Config { if cfg.KeyGenerator == nil { cfg.KeyGenerator = ConfigDefault.KeyGenerator } + if len(cfg.Methods) == 0 { + cfg.Methods = ConfigDefault.Methods + } return cfg } diff --git a/middleware/cache/manager.go b/middleware/cache/manager.go index 7fe3e58cea..5877fb19c9 100644 --- a/middleware/cache/manager.go +++ b/middleware/cache/manager.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/internal/storage/memory" + "github.com/gofiber/fiber/v3/internal/memory" ) // go:generate msgp @@ -24,6 +24,7 @@ type item struct { //msgp:ignore manager type manager struct { pool sync.Pool + memory *memory.Storage storage fiber.Storage } @@ -31,20 +32,18 @@ func newManager(storage fiber.Storage) *manager { // Create new storage handler manager := &manager{ pool: sync.Pool{ - New: func() any { + New: func() interface{} { return new(item) }, }, } - if storage != nil { // Use provided storage if provided manager.storage = storage } else { // Fallback to memory storage - manager.storage = memory.New(1) + manager.memory = memory.New() } - return manager } @@ -69,13 +68,18 @@ func (m *manager) release(e *item) { // get data from storage or memory func (m *manager) get(key string) (it *item) { - it = m.acquire() - if raw, _ := m.storage.Get(key); raw != nil { - if _, err := it.UnmarshalMsg(raw); err != nil { - return + if m.storage != nil { + it = m.acquire() + if raw, _ := m.storage.Get(key); raw != nil { + if _, err := it.UnmarshalMsg(raw); err != nil { + return + } } + return + } + if it, _ = m.memory.Get(key).(*item); it == nil { + it = m.acquire() } - return } @@ -83,24 +87,37 @@ func (m *manager) get(key string) (it *item) { func (m *manager) getRaw(key string) (raw []byte) { if m.storage != nil { raw, _ = m.storage.Get(key) + } else { + raw, _ = m.memory.Get(key).([]byte) } - return } // set data to storage or memory func (m *manager) set(key string, it *item, exp time.Duration) { - if raw, err := it.MarshalMsg(nil); err == nil { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + if raw, err := it.MarshalMsg(nil); err == nil { + _ = m.storage.Set(key, raw, exp) + } + } else { + m.memory.Set(key, it, exp) } } // set data to storage or memory func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + _ = m.storage.Set(key, raw, exp) + } else { + m.memory.Set(key, raw, exp) + } } // delete data from storage or memory func (m *manager) delete(key string) { - _ = m.storage.Delete(key) + if m.storage != nil { + _ = m.storage.Delete(key) + } else { + m.memory.Delete(key) + } } diff --git a/middleware/compress/README.md b/middleware/compress/README.md index 15d2df0293..c5b89046eb 100644 --- a/middleware/compress/README.md +++ b/middleware/compress/README.md @@ -23,8 +23,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/compress" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/compress" ) ``` diff --git a/middleware/compress/compress_test.go b/middleware/compress/compress_test.go index 697c49218d..4609eafc88 100644 --- a/middleware/compress/compress_test.go +++ b/middleware/compress/compress_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) var filedata []byte @@ -37,14 +37,14 @@ func Test_Compress_Gzip(t *testing.T) { req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, "gzip", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, "gzip", resp.Header.Get(fiber.HeaderContentEncoding)) // Validate that the file size has shrunk body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, len(body) < len(filedata)) + require.NoError(t, err) + require.True(t, len(body) < len(filedata)) } // go test -run Test_Compress_Different_Level @@ -65,14 +65,14 @@ func Test_Compress_Different_Level(t *testing.T) { req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, "gzip", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, "gzip", resp.Header.Get(fiber.HeaderContentEncoding)) // Validate that the file size has shrunk body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, len(body) < len(filedata)) + require.NoError(t, err) + require.True(t, len(body) < len(filedata)) }) } } @@ -90,14 +90,14 @@ func Test_Compress_Deflate(t *testing.T) { req.Header.Set("Accept-Encoding", "deflate") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, "deflate", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, "deflate", resp.Header.Get(fiber.HeaderContentEncoding)) // Validate that the file size has shrunk body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, len(body) < len(filedata)) + require.NoError(t, err) + require.True(t, len(body) < len(filedata)) } func Test_Compress_Brotli(t *testing.T) { @@ -113,14 +113,14 @@ func Test_Compress_Brotli(t *testing.T) { req.Header.Set("Accept-Encoding", "br") resp, err := app.Test(req, 10000) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, "br", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, "br", resp.Header.Get(fiber.HeaderContentEncoding)) // Validate that the file size has shrunk body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, len(body) < len(filedata)) + require.NoError(t, err) + require.True(t, len(body) < len(filedata)) } func Test_Compress_Disabled(t *testing.T) { @@ -136,14 +136,14 @@ func Test_Compress_Disabled(t *testing.T) { req.Header.Set("Accept-Encoding", "br") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, "", resp.Header.Get(fiber.HeaderContentEncoding)) // Validate the file size is not shrunk body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, len(body) == len(filedata)) + require.NoError(t, err) + require.True(t, len(body) == len(filedata)) } func Test_Compress_Next_Error(t *testing.T) { @@ -159,13 +159,13 @@ func Test_Compress_Next_Error(t *testing.T) { req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 500, resp.StatusCode, "Status code") - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderContentEncoding)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 500, resp.StatusCode, "Status code") + require.Equal(t, "", resp.Header.Get(fiber.HeaderContentEncoding)) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "next error", string(body)) + require.NoError(t, err) + require.Equal(t, "next error", string(body)) } // go test -run Test_Compress_Next @@ -178,6 +178,6 @@ func Test_Compress_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } diff --git a/middleware/cors/README.md b/middleware/cors/README.md index a4e9241cf1..428984f8e8 100644 --- a/middleware/cors/README.md +++ b/middleware/cors/README.md @@ -25,8 +25,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" ) ``` diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 64b019006e..84e7bd9b73 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -33,18 +33,18 @@ func testDefaultOrEmptyConfig(t *testing.T, app *fiber.App) { ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) - utils.AssertEqual(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlExposeHeaders))) + require.Equal(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlExposeHeaders))) // Test default OPTIONS (preflight) response headers ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod(fiber.MethodOptions) h(ctx) - utils.AssertEqual(t, "GET,POST,HEAD,PUT,DELETE,PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlMaxAge))) + require.Equal(t, "GET,POST,HEAD,PUT,DELETE,PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlMaxAge))) } // go test -run -v Test_CORS_Wildcard @@ -72,18 +72,18 @@ func Test_CORS_Wildcard(t *testing.T) { handler(ctx) // Check result - utils.AssertEqual(t, "localhost", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) - utils.AssertEqual(t, "true", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) - utils.AssertEqual(t, "3600", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlMaxAge))) - utils.AssertEqual(t, "Authentication", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) + require.Equal(t, "localhost", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "true", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) + require.Equal(t, "3600", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlMaxAge))) + require.Equal(t, "Authentication", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowHeaders))) // Test non OPTIONS (preflight) response headers ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod(fiber.MethodGet) handler(ctx) - utils.AssertEqual(t, "true", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) - utils.AssertEqual(t, "X-Request-ID", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlExposeHeaders))) + require.Equal(t, "true", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) + require.Equal(t, "X-Request-ID", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlExposeHeaders))) } // go test -run -v Test_CORS_Subdomain @@ -106,7 +106,7 @@ func Test_CORS_Subdomain(t *testing.T) { handler(ctx) // Allow-Origin header should be "" because http://google.com does not satisfy http://*.example.com - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) ctx.Request.Reset() ctx.Response.Reset() @@ -118,7 +118,7 @@ func Test_CORS_Subdomain(t *testing.T) { handler(ctx) - utils.AssertEqual(t, "http://test.example.com", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "http://test.example.com", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) } func Test_CORS_AllowOriginScheme(t *testing.T) { @@ -215,9 +215,9 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { handler(ctx) if tt.shouldAllowOrigin { - utils.AssertEqual(t, tt.reqOrigin, string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, tt.reqOrigin, string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) } else { - utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + require.Equal(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) } } } @@ -232,6 +232,6 @@ func Test_CORS_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } diff --git a/middleware/csrf/README.md b/middleware/csrf/README.md index c5618ab534..e25b828395 100644 --- a/middleware/csrf/README.md +++ b/middleware/csrf/README.md @@ -29,8 +29,8 @@ Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/crsf" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/crsf" ) ``` @@ -49,9 +49,12 @@ app.Use(csrf.New(csrf.Config{ CookieSameSite: "Lax", Expiration: 1 * time.Hour, KeyGenerator: utils.UUID, + Extractor: func(c *fiber.Ctx) (string, error) { ... }, })) ``` +Note: KeyLookup will be ignored if Extractor is explicitly set. + ### Custom Storage/Database You can use any storage from our [storage](https://github.com/gofiber/storage/) package. @@ -74,7 +77,7 @@ type Config struct { Next func(c fiber.Ctx) bool // KeyLookup is a string in the form of ":" that is used - // to extract token from the request. + // to create an Extractor that extracts the token from the request. // Possible values: // - "header:" // - "query:" @@ -82,6 +85,8 @@ type Config struct { // - "form:" // - "cookie:" // + // Ignored if an Extractor is explicitly set. + // // Optional. Default: "header:X-CSRF-Token" KeyLookup string @@ -133,6 +138,13 @@ type Config struct { // // Optional. Default: utils.UUID KeyGenerator func() string + + // Extractor returns the csrf token + // + // If set this will be used in place of an Extractor based on KeyLookup. + // + // Optional. Default will create an Extractor based on KeyLookup. + Extractor func(c *fiber.Ctx) (string, error) } ``` diff --git a/middleware/csrf/config.go b/middleware/csrf/config.go index fbf6060b2b..c8c81129c0 100644 --- a/middleware/csrf/config.go +++ b/middleware/csrf/config.go @@ -6,7 +6,7 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // Config defines the config for middleware. @@ -17,7 +17,7 @@ type Config struct { Next func(c fiber.Ctx) bool // KeyLookup is a string in the form of ":" that is used - // to extract token from the request. + // to create an Extractor that extracts the token from the request. // Possible values: // - "header:" // - "query:" @@ -25,6 +25,8 @@ type Config struct { // - "form:" // - "cookie:" // + // Ignored if an Extractor is explicitly set. + // // Optional. Default: "header:X-CSRF-Token" KeyLookup string @@ -82,19 +84,25 @@ type Config struct { // Optional. Default: DefaultErrorHandler ErrorHandler fiber.ErrorHandler - // extractor returns the csrf token from the request based on KeyLookup - extractor func(c fiber.Ctx) (string, error) + // Extractor returns the csrf token + // + // If set this will be used in place of an Extractor based on KeyLookup. + // + // Optional. Default will create an Extractor based on KeyLookup. + Extractor func(c fiber.Ctx) (string, error) } +const HeaderName = "X-Csrf-Token" + // ConfigDefault is the default config var ConfigDefault = Config{ - KeyLookup: "header:X-Csrf-Token", + KeyLookup: "header:" + HeaderName, CookieName: "csrf_", CookieSameSite: "Lax", Expiration: 1 * time.Hour, KeyGenerator: utils.UUID, ErrorHandler: defaultErrorHandler, - extractor: csrfFromHeader("X-Csrf-Token"), + Extractor: CsrfFromHeader(HeaderName), } // default ErrorHandler that process return error from fiber.Handler @@ -139,18 +147,20 @@ func configDefault(config ...Config) Config { panic("[CSRF] KeyLookup must in the form of :") } - // By default we extract from a header - cfg.extractor = csrfFromHeader(textproto.CanonicalMIMEHeaderKey(selectors[1])) - - switch selectors[0] { - case "form": - cfg.extractor = csrfFromForm(selectors[1]) - case "query": - cfg.extractor = csrfFromQuery(selectors[1]) - case "param": - cfg.extractor = csrfFromParam(selectors[1]) - case "cookie": - cfg.extractor = csrfFromCookie(selectors[1]) + if cfg.Extractor == nil { + // By default we extract from a header + cfg.Extractor = CsrfFromHeader(textproto.CanonicalMIMEHeaderKey(selectors[1])) + + switch selectors[0] { + case "form": + cfg.Extractor = CsrfFromForm(selectors[1]) + case "query": + cfg.Extractor = CsrfFromQuery(selectors[1]) + case "param": + cfg.Extractor = CsrfFromParam(selectors[1]) + case "cookie": + cfg.Extractor = CsrfFromCookie(selectors[1]) + } } return cfg diff --git a/middleware/csrf/csrf.go b/middleware/csrf/csrf.go index 844da0f0fb..29512daba9 100644 --- a/middleware/csrf/csrf.go +++ b/middleware/csrf/csrf.go @@ -39,7 +39,7 @@ func New(config ...Config) fiber.Handler { // Assume that anything not defined as 'safe' by RFC7231 needs protection // Extract token from client request i.e. header, query, param, form or cookie - token, err = cfg.extractor(c) + token, err = cfg.Extractor(c) if err != nil { return cfg.ErrorHandler(c, err) } diff --git a/middleware/csrf/csrf_test.go b/middleware/csrf/csrf_test.go index e95fa7aac3..fd3e89a99b 100644 --- a/middleware/csrf/csrf_test.go +++ b/middleware/csrf/csrf_test.go @@ -6,7 +6,8 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -34,15 +35,15 @@ func Test_CSRF(t *testing.T) { ctx.Response.Reset() ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Empty/invalid CSRF token ctx.Request.Reset() ctx.Response.Reset() ctx.Request.Header.SetMethod("POST") - ctx.Request.Header.Set("X-CSRF-Token", "johndoe") + ctx.Request.Header.Set(HeaderName, "johndoe") h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Valid CSRF token ctx.Request.Reset() @@ -55,9 +56,9 @@ func Test_CSRF(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() ctx.Request.Header.SetMethod("POST") - ctx.Request.Header.Set("X-CSRF-Token", token) + ctx.Request.Header.Set(HeaderName, token) h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) + require.Equal(t, 200, ctx.Response.StatusCode()) } } @@ -71,13 +72,13 @@ func Test_CSRF_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_CSRF_Invalid_KeyLookup(t *testing.T) { defer func() { - utils.AssertEqual(t, "[CSRF] KeyLookup must in the form of :", recover()) + require.Equal(t, "[CSRF] KeyLookup must in the form of :", recover()) }() app := fiber.New() @@ -109,7 +110,7 @@ func Test_CSRF_From_Form(t *testing.T) { ctx.Request.Header.SetMethod("POST") ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm) h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Generate CSRF token ctx.Request.Reset() @@ -123,7 +124,7 @@ func Test_CSRF_From_Form(t *testing.T) { ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm) ctx.Request.SetBodyString("_csrf=" + token) h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) + require.Equal(t, 200, ctx.Response.StatusCode()) } func Test_CSRF_From_Query(t *testing.T) { @@ -142,7 +143,7 @@ func Test_CSRF_From_Query(t *testing.T) { ctx.Request.Header.SetMethod("POST") ctx.Request.SetRequestURI("/?_csrf=" + utils.UUID()) h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Generate CSRF token ctx.Request.Reset() @@ -158,8 +159,8 @@ func Test_CSRF_From_Query(t *testing.T) { ctx.Request.SetRequestURI("/?_csrf=" + token) ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "OK", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "OK", string(ctx.Response.Body())) } func Test_CSRF_From_Param(t *testing.T) { @@ -178,7 +179,7 @@ func Test_CSRF_From_Param(t *testing.T) { ctx.Request.Header.SetMethod("POST") ctx.Request.SetRequestURI("/" + utils.UUID()) h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Generate CSRF token ctx.Request.Reset() @@ -194,8 +195,8 @@ func Test_CSRF_From_Param(t *testing.T) { ctx.Request.SetRequestURI("/" + token) ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "OK", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "OK", string(ctx.Response.Body())) } func Test_CSRF_From_Cookie(t *testing.T) { @@ -215,7 +216,7 @@ func Test_CSRF_From_Cookie(t *testing.T) { ctx.Request.SetRequestURI("/") ctx.Request.Header.Set(fiber.HeaderCookie, "csrf="+utils.UUID()+";") h(ctx) - utils.AssertEqual(t, 403, ctx.Response.StatusCode()) + require.Equal(t, 403, ctx.Response.StatusCode()) // Generate CSRF token ctx.Request.Reset() @@ -232,15 +233,59 @@ func Test_CSRF_From_Cookie(t *testing.T) { ctx.Request.Header.Set(fiber.HeaderCookie, "csrf="+token+";") ctx.Request.SetRequestURI("/") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "OK", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "OK", string(ctx.Response.Body())) +} + +func Test_CSRF_From_Custom(t *testing.T) { + app := fiber.New() + + extractor := func(c fiber.Ctx) (string, error) { + body := string(c.Body()) + // Generate the correct extractor to get the token from the correct location + selectors := strings.Split(body, "=") + + if len(selectors) != 2 || selectors[1] == "" { + return "", errMissingParam + } + return selectors[1], nil + } + + app.Use(New(Config{Extractor: extractor})) + + app.Post("/", func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + // Invalid CSRF token + ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMETextPlain) + h(ctx) + require.Equal(t, 403, ctx.Response.StatusCode()) + + // Generate CSRF token + ctx.Request.Reset() + ctx.Response.Reset() + ctx.Request.Header.SetMethod("GET") + h(ctx) + token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) + token = strings.Split(strings.Split(token, ";")[0], "=")[1] + + ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMETextPlain) + ctx.Request.SetBodyString("_csrf=" + token) + h(ctx) + require.Equal(t, 200, ctx.Response.StatusCode()) } func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) { app := fiber.New() errHandler := func(ctx fiber.Ctx, err error) error { - utils.AssertEqual(t, errTokenNotFound, err) + require.Equal(t, errTokenNotFound, err) return ctx.Status(419).Send([]byte("invalid CSRF token")) } @@ -261,17 +306,17 @@ func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() ctx.Request.Header.SetMethod("POST") - ctx.Request.Header.Set("X-CSRF-Token", "johndoe") + ctx.Request.Header.Set(HeaderName, "johndoe") h(ctx) - utils.AssertEqual(t, 419, ctx.Response.StatusCode()) - utils.AssertEqual(t, "invalid CSRF token", string(ctx.Response.Body())) + require.Equal(t, 419, ctx.Response.StatusCode()) + require.Equal(t, "invalid CSRF token", string(ctx.Response.Body())) } func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) { app := fiber.New() errHandler := func(ctx fiber.Ctx, err error) error { - utils.AssertEqual(t, errMissingHeader, err) + require.Equal(t, errMissingHeader, err) return ctx.Status(419).Send([]byte("empty CSRF token")) } @@ -293,6 +338,114 @@ func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) { ctx.Response.Reset() ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 419, ctx.Response.StatusCode()) - utils.AssertEqual(t, "empty CSRF token", string(ctx.Response.Body())) + require.Equal(t, 419, ctx.Response.StatusCode()) + require.Equal(t, "empty CSRF token", string(ctx.Response.Body())) +} + +// TODO: use this test case and make the unsafe header value bug from https://github.com/gofiber/fiber/issues/2045 reproducible and permanently fixed/tested by this testcase +//func Test_CSRF_UnsafeHeaderValue(t *testing.T) { +// app := fiber.New() +// +// app.Use(New()) +// app.Get("/", func(c fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) +// app.Get("/test", func(c fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) +// app.Post("/", func(c fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) +// +// resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) +// utils.AssertEqual(t, nil, err) +// utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) +// +// var token string +// for _, c := range resp.Cookies() { +// if c.Name != ConfigDefault.CookieName { +// continue +// } +// token = c.Value +// break +// } +// +// fmt.Println("token", token) +// +// getReq := httptest.NewRequest(http.MethodGet, "/", nil) +// getReq.Header.Set(HeaderName, token) +// resp, err = app.Test(getReq) +// +// getReq = httptest.NewRequest(http.MethodGet, "/test", nil) +// getReq.Header.Set("X-Requested-With", "XMLHttpRequest") +// getReq.Header.Set(fiber.HeaderCacheControl, "no") +// getReq.Header.Set(HeaderName, token) +// +// resp, err = app.Test(getReq) +// +// getReq.Header.Set(fiber.HeaderAccept, "*/*") +// getReq.Header.Del(HeaderName) +// resp, err = app.Test(getReq) +// +// postReq := httptest.NewRequest(http.MethodPost, "/", nil) +// postReq.Header.Set("X-Requested-With", "XMLHttpRequest") +// postReq.Header.Set(HeaderName, token) +// resp, err = app.Test(postReq) +//} + +// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_Check -benchmem -count=4 +func Benchmark_Middleware_CSRF_Check(b *testing.B) { + app := fiber.New() + + app.Use(New()) + app.Get("/", func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusTeapot) + }) + + fctx := &fasthttp.RequestCtx{} + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + // Generate CSRF token + ctx.Request.Header.SetMethod("GET") + h(ctx) + token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) + token = strings.Split(strings.Split(token, ";")[0], "=")[1] + + ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.Set(HeaderName, token) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + h(fctx) + } + + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) +} + +// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_GenerateToken -benchmem -count=4 +func Benchmark_Middleware_CSRF_GenerateToken(b *testing.B) { + app := fiber.New() + + app.Use(New()) + app.Get("/", func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusTeapot) + }) + + fctx := &fasthttp.RequestCtx{} + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + // Generate CSRF token + ctx.Request.Header.SetMethod("GET") + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + h(fctx) + } + + require.Equal(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) } diff --git a/middleware/csrf/extractors.go b/middleware/csrf/extractors.go index c971ac481f..0919dffdfc 100644 --- a/middleware/csrf/extractors.go +++ b/middleware/csrf/extractors.go @@ -15,7 +15,7 @@ var ( ) // csrfFromParam returns a function that extracts token from the url param string. -func csrfFromParam(param string) func(c fiber.Ctx) (string, error) { +func CsrfFromParam(param string) func(c fiber.Ctx) (string, error) { return func(c fiber.Ctx) (string, error) { token := c.Params(param) if token == "" { @@ -26,7 +26,7 @@ func csrfFromParam(param string) func(c fiber.Ctx) (string, error) { } // csrfFromForm returns a function that extracts a token from a multipart-form. -func csrfFromForm(param string) func(c fiber.Ctx) (string, error) { +func CsrfFromForm(param string) func(c fiber.Ctx) (string, error) { return func(c fiber.Ctx) (string, error) { token := c.FormValue(param) if token == "" { @@ -37,7 +37,7 @@ func csrfFromForm(param string) func(c fiber.Ctx) (string, error) { } // csrfFromCookie returns a function that extracts token from the cookie header. -func csrfFromCookie(param string) func(c fiber.Ctx) (string, error) { +func CsrfFromCookie(param string) func(c fiber.Ctx) (string, error) { return func(c fiber.Ctx) (string, error) { token := c.Cookies(param) if token == "" { @@ -48,7 +48,7 @@ func csrfFromCookie(param string) func(c fiber.Ctx) (string, error) { } // csrfFromHeader returns a function that extracts token from the request header. -func csrfFromHeader(param string) func(c fiber.Ctx) (string, error) { +func CsrfFromHeader(param string) func(c fiber.Ctx) (string, error) { return func(c fiber.Ctx) (string, error) { token := c.Get(param) if token == "" { @@ -59,7 +59,7 @@ func csrfFromHeader(param string) func(c fiber.Ctx) (string, error) { } // csrfFromQuery returns a function that extracts token from the query string. -func csrfFromQuery(param string) func(c fiber.Ctx) (string, error) { +func CsrfFromQuery(param string) func(c fiber.Ctx) (string, error) { return func(c fiber.Ctx) (string, error) { token := c.Query(param) if token == "" { diff --git a/middleware/csrf/manager.go b/middleware/csrf/manager.go index 561117fe70..0b80f35e06 100644 --- a/middleware/csrf/manager.go +++ b/middleware/csrf/manager.go @@ -5,7 +5,8 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/internal/storage/memory" + "github.com/gofiber/fiber/v3/internal/memory" + "github.com/gofiber/utils" ) // go:generate msgp @@ -15,6 +16,7 @@ type item struct{} //msgp:ignore manager type manager struct { pool sync.Pool + memory *memory.Storage storage fiber.Storage } @@ -22,20 +24,18 @@ func newManager(storage fiber.Storage) *manager { // Create new storage handler manager := &manager{ pool: sync.Pool{ - New: func() any { + New: func() interface{} { return new(item) }, }, } - if storage != nil { // Use provided storage if provided manager.storage = storage } else { - // Fallback to memory storage - manager.storage = memory.New(1) + // Fallback too memory storage + manager.memory = memory.New() } - return manager } @@ -46,41 +46,67 @@ func (m *manager) acquire() *item { // release and reset *entry to sync.Pool func (m *manager) release(e *item) { + // don't release item if we using memory storage + if m.storage != nil { + return + } m.pool.Put(e) } // get data from storage or memory func (m *manager) get(key string) (it *item) { - it = m.acquire() - if raw, _ := m.storage.Get(key); raw != nil { - if _, err := it.UnmarshalMsg(raw); err != nil { - return + if m.storage != nil { + it = m.acquire() + if raw, _ := m.storage.Get(key); raw != nil { + if _, err := it.UnmarshalMsg(raw); err != nil { + return + } } + return + } + if it, _ = m.memory.Get(key).(*item); it == nil { + it = m.acquire() } - return } // get raw data from storage or memory func (m *manager) getRaw(key string) (raw []byte) { - raw, _ = m.storage.Get(key) - + if m.storage != nil { + raw, _ = m.storage.Get(key) + } else { + raw, _ = m.memory.Get(key).([]byte) + } return } // set data to storage or memory func (m *manager) set(key string, it *item, exp time.Duration) { - if raw, err := it.MarshalMsg(nil); err == nil { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + if raw, err := it.MarshalMsg(nil); err == nil { + _ = m.storage.Set(key, raw, exp) + } + } else { + // the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here + m.memory.Set(utils.CopyString(key), it, exp) } } // set data to storage or memory func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + _ = m.storage.Set(key, raw, exp) + } else { + // the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here + m.memory.Set(utils.CopyString(key), raw, exp) + } } // delete data from storage or memory func (m *manager) delete(key string) { - _ = m.storage.Delete(key) + if m.storage != nil { + _ = m.storage.Delete(key) + } else { + m.memory.Delete(key) + } } diff --git a/middleware/encryptcookie/README.md b/middleware/encryptcookie/README.md index 72b7e2cc53..80bdfff8c2 100644 --- a/middleware/encryptcookie/README.md +++ b/middleware/encryptcookie/README.md @@ -25,8 +25,8 @@ Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/encryptcookie" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/encryptcookie" ) ``` @@ -95,3 +95,19 @@ app.Use(encryptcookie.New(encryptcookie.Config{ Key: "secret-thirty-2-character-string", })) ``` + +## Usage of CSRF and Encryptcookie Middlewares with Custom Cookie Names +Normally, encryptcookie middleware skips `csrf_` cookies. However, it won't work when you use custom cookie names for CSRF. You should update `Except` config to avoid this problem. For example: + +```go +app.Use(encryptcookie.New(encryptcookie.Config{ + Key: "secret-thirty-2-character-string", + Except: []string{"csrf_1"}, // exclude CSRF cookie +})) + +app.Use(csrf.New(csrf.Config{ + KeyLookup: "form:test", + CookieName: "csrf_1", + CookieHTTPOnly: true, +})) +``` \ No newline at end of file diff --git a/middleware/encryptcookie/encryptcookie_test.go b/middleware/encryptcookie/encryptcookie_test.go index 2695781f8a..c4d400e94c 100644 --- a/middleware/encryptcookie/encryptcookie_test.go +++ b/middleware/encryptcookie/encryptcookie_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -36,39 +36,39 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) { ctx := &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("GET") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "value=", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "value=", string(ctx.Response.Body())) // Test invalid cookie ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("GET") ctx.Request.Header.SetCookie("test", "Invalid") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "value=", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "value=", string(ctx.Response.Body())) ctx.Request.Header.SetCookie("test", "ixQURE2XOyZUs0WAOh2ehjWcP7oZb07JvnhWOsmeNUhPsj4+RyI=") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "value=", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "value=", string(ctx.Response.Body())) // Test valid cookie ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) + require.Equal(t, 200, ctx.Response.StatusCode()) encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test") - utils.AssertEqual(t, true, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") + require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") decryptedCookieValue, _ := DecryptCookie(string(encryptedCookie.Value()), testKey) - utils.AssertEqual(t, "SomeThing", decryptedCookieValue) + require.Equal(t, "SomeThing", decryptedCookieValue) ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("GET") ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value())) h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "value=SomeThing", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "value=SomeThing", string(ctx.Response.Body())) } func Test_Encrypt_Cookie_Next(t *testing.T) { @@ -90,8 +90,8 @@ func Test_Encrypt_Cookie_Next(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "SomeThing", resp.Cookies()[0].Value) + require.NoError(t, err) + require.Equal(t, "SomeThing", resp.Cookies()[0].Value) } func Test_Encrypt_Cookie_Except(t *testing.T) { @@ -122,18 +122,18 @@ func Test_Encrypt_Cookie_Except(t *testing.T) { ctx := &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("GET") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) + require.Equal(t, 200, ctx.Response.StatusCode()) rawCookie := fasthttp.Cookie{} rawCookie.SetKey("test1") - utils.AssertEqual(t, true, ctx.Response.Header.Cookie(&rawCookie), "Get cookie value") - utils.AssertEqual(t, "SomeThing", string(rawCookie.Value())) + require.True(t, ctx.Response.Header.Cookie(&rawCookie), "Get cookie value") + require.Equal(t, "SomeThing", string(rawCookie.Value())) encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test2") - utils.AssertEqual(t, true, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") + require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") decryptedCookieValue, _ := DecryptCookie(string(encryptedCookie.Value()), testKey) - utils.AssertEqual(t, "SomeThing", decryptedCookieValue) + require.Equal(t, "SomeThing", decryptedCookieValue) } func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) { @@ -167,18 +167,18 @@ func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) { ctx := &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("POST") h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) + require.Equal(t, 200, ctx.Response.StatusCode()) encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test") - utils.AssertEqual(t, true, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") + require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") decodedBytes, _ := base64.StdEncoding.DecodeString(string(encryptedCookie.Value())) - utils.AssertEqual(t, "SomeThing", string(decodedBytes)) + require.Equal(t, "SomeThing", string(decodedBytes)) ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod("GET") ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value())) h(ctx) - utils.AssertEqual(t, 200, ctx.Response.StatusCode()) - utils.AssertEqual(t, "value=SomeThing", string(ctx.Response.Body())) + require.Equal(t, 200, ctx.Response.StatusCode()) + require.Equal(t, "value=SomeThing", string(ctx.Response.Body())) } diff --git a/middleware/envvar/README.md b/middleware/envvar/README.md new file mode 100644 index 0000000000..a16923d048 --- /dev/null +++ b/middleware/envvar/README.md @@ -0,0 +1,83 @@ +# Exposing Environment Variables Middleware + +EnvVar middleware for [Fiber](https://github.com/gofiber/fiber) that can be used to expose environment variables with various options. + +## Table of Contents + +- [Environment Variables (EnvVar) Middleware](#environment-variables-envvar-middleware) + - [Table of Contents](#table-of-contents) + - [Signatures](#signatures) + - [Examples](#examples) + - [Default Config](#default-config) + - [Custom Config](#custom-config) + - [Response](#response) + - [Config](#config) + - [Default Config](#default-config-1) + +## Signatures + +```go +func New(config ...Config) fiber.Handler +``` + +## Examples + +First import the middleware from Fiber, + +```go +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/envvar" +) +``` + +Then create a Fiber app with `app := fiber.New()`. + +**Note**: You need to provide a path to use envvar middleware. + +### Default Config + +```go +app.Use("/expose/envvars", envvar.New()) +``` + +### Custom Config + +```go +app.Use("/expose/envvars", envvar.New( + envvar.Config{ + ExportVars: map[string]string{"testKey": "", "testDefaultKey": "testDefaultVal"}, + ExcludeVars: map[string]string{"excludeKey": ""}, + }), +) +``` + +### Response + +Http response contract: +``` +{ + "vars": { + "someEnvVariable": "someValue", + "anotherEnvVariable": "anotherValue" + } +} +``` + +## Config + +```go +// Config defines the config for middleware. +type Config struct { + // ExportVars specifies the environment variables that should export + ExportVars map[string]string + // ExcludeVars specifies the environment variables that should not export + ExcludeVars map[string]string +} +``` + +## Default Config + +```go +Config{} +``` diff --git a/middleware/envvar/envvar.go b/middleware/envvar/envvar.go new file mode 100644 index 0000000000..47cd93f1c1 --- /dev/null +++ b/middleware/envvar/envvar.go @@ -0,0 +1,69 @@ +package envvar + +import ( + "os" + "strings" + + "github.com/gofiber/fiber/v3" +) + +// Config defines the config for middleware. +type Config struct { + // ExportVars specifies the environment variables that should export + ExportVars map[string]string + // ExcludeVars specifies the environment variables that should not export + ExcludeVars map[string]string +} + +type EnvVar struct { + Vars map[string]string `json:"vars"` +} + +func (envVar *EnvVar) set(key, val string) { + envVar.Vars[key] = val +} + +var defaultConfig = Config{} + +func New(config ...Config) fiber.Handler { + var cfg = defaultConfig + if len(config) > 0 { + cfg = config[0] + } + + return func(c fiber.Ctx) error { + if c.Method() != fiber.MethodGet { + return fiber.ErrMethodNotAllowed + } + + envVar := newEnvVar(cfg) + varsByte, err := c.App().Config().JSONEncoder(envVar) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) + } + c.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSONCharsetUTF8) + return c.Send(varsByte) + } +} + +func newEnvVar(cfg Config) *EnvVar { + vars := &EnvVar{Vars: make(map[string]string)} + + if len(cfg.ExportVars) > 0 { + for key, defaultVal := range cfg.ExportVars { + vars.set(key, defaultVal) + if envVal, exists := os.LookupEnv(key); exists { + vars.set(key, envVal) + } + } + } else { + for _, envVal := range os.Environ() { + keyVal := strings.SplitN(envVal, "=", 2) + if _, exists := cfg.ExcludeVars[keyVal[0]]; !exists { + vars.set(keyVal[0], keyVal[1]) + } + } + } + + return vars +} diff --git a/middleware/envvar/envvar_test.go b/middleware/envvar/envvar_test.go new file mode 100644 index 0000000000..527431d2e0 --- /dev/null +++ b/middleware/envvar/envvar_test.go @@ -0,0 +1,133 @@ +package envvar + +import ( + "encoding/json" + "io" + "net/http" + "testing" + + "github.com/gofiber/fiber/v3" + "github.com/stretchr/testify/require" +) + +func TestEnvVarStructWithExportVarsExcludeVars(t *testing.T) { + t.Setenv("testKey", "testEnvValue") + t.Setenv("anotherEnvKey", "anotherEnvVal") + t.Setenv("excludeKey", "excludeEnvValue") + + vars := newEnvVar(Config{ + ExportVars: map[string]string{"testKey": "", "testDefaultKey": "testDefaultVal"}, + ExcludeVars: map[string]string{"excludeKey": ""}}) + + require.Equal(t, vars.Vars["testKey"], "testEnvValue") + require.Equal(t, vars.Vars["testDefaultKey"], "testDefaultVal") + require.Equal(t, vars.Vars["excludeKey"], "") + require.Equal(t, vars.Vars["anotherEnvKey"], "") +} + +func TestEnvVarHandler(t *testing.T) { + t.Setenv("testKey", "testVal") + + expectedEnvVarResponse, _ := json.Marshal( + struct { + Vars map[string]string `json:"vars"` + }{ + map[string]string{"testKey": "testVal"}, + }) + + app := fiber.New() + app.Use("/envvars", New(Config{ + ExportVars: map[string]string{"testKey": ""}})) + + req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + resp, err := app.Test(req) + require.Equal(t, nil, err) + + respBody, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + + require.Equal(t, expectedEnvVarResponse, respBody) +} + +func TestEnvVarHandlerNotMatched(t *testing.T) { + app := fiber.New() + app.Use("/envvars", New(Config{ + ExportVars: map[string]string{"testKey": ""}})) + + app.Get("/another-path", func(ctx fiber.Ctx) error { + require.NoError(t, ctx.SendString("OK")) + return nil + }) + + req, _ := http.NewRequest("GET", "http://localhost/another-path", nil) + resp, err := app.Test(req) + require.Equal(t, nil, err) + + respBody, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + + require.Equal(t, []byte("OK"), respBody) +} + +func TestEnvVarHandlerDefaultConfig(t *testing.T) { + t.Setenv("testEnvKey", "testEnvVal") + + app := fiber.New() + app.Use("/envvars", New()) + + req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + resp, err := app.Test(req) + require.Equal(t, nil, err) + + respBody, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + + var envVars EnvVar + require.Equal(t, nil, json.Unmarshal(respBody, &envVars)) + val := envVars.Vars["testEnvKey"] + require.Equal(t, "testEnvVal", val) +} + +func TestEnvVarHandlerMethod(t *testing.T) { + app := fiber.New() + app.Use("/envvars", New()) + + req, _ := http.NewRequest("POST", "http://localhost/envvars", nil) + resp, err := app.Test(req) + require.Equal(t, nil, err) + require.Equal(t, fiber.StatusMethodNotAllowed, resp.StatusCode) +} + +func TestEnvVarHandlerSpecialValue(t *testing.T) { + testEnvKey := "testEnvKey" + fakeBase64 := "testBase64:TQ==" + t.Setenv(testEnvKey, fakeBase64) + + app := fiber.New() + app.Use("/envvars", New()) + app.Use("/envvars/export", New(Config{ExportVars: map[string]string{testEnvKey: ""}})) + + req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + resp, err := app.Test(req) + require.Equal(t, nil, err) + + respBody, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + + var envVars EnvVar + require.Equal(t, nil, json.Unmarshal(respBody, &envVars)) + val := envVars.Vars[testEnvKey] + require.Equal(t, fakeBase64, val) + + req, _ = http.NewRequest("GET", "http://localhost/envvars/export", nil) + resp, err = app.Test(req) + require.Equal(t, nil, err) + + respBody, err = io.ReadAll(resp.Body) + require.Equal(t, nil, err) + + var envVarsExport EnvVar + require.Equal(t, nil, json.Unmarshal(respBody, &envVarsExport)) + val = envVarsExport.Vars[testEnvKey] + require.Equal(t, fakeBase64, val) +} diff --git a/middleware/etag/README.md b/middleware/etag/README.md index b7c283aa85..7026a2a355 100644 --- a/middleware/etag/README.md +++ b/middleware/etag/README.md @@ -25,8 +25,8 @@ Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/etag" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/etag" ) ``` diff --git a/middleware/etag/etag_test.go b/middleware/etag/etag_test.go index 44b834bc4b..4768eb5793 100644 --- a/middleware/etag/etag_test.go +++ b/middleware/etag/etag_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -21,8 +21,8 @@ func Test_ETag_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_ETag_SkipError @@ -36,8 +36,8 @@ func Test_ETag_SkipError(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusForbidden, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusForbidden, resp.StatusCode) } // go test -run Test_ETag_NotStatusOK @@ -51,8 +51,8 @@ func Test_ETag_NotStatusOK(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusCreated, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusCreated, resp.StatusCode) } // go test -run Test_ETag_NoBody @@ -66,8 +66,8 @@ func Test_ETag_NoBody(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) } // go test -run Test_ETag_NewEtag @@ -104,19 +104,19 @@ func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { } resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if !headerIfNoneMatch || !matched { - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, `"13-1831710635"`, resp.Header.Get(fiber.HeaderETag)) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, `"13-1831710635"`, resp.Header.Get(fiber.HeaderETag)) return } if matched { - utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode) + require.Equal(t, fiber.StatusNotModified, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 0, len(b)) + require.NoError(t, err) + require.Equal(t, 0, len(b)) } } @@ -154,19 +154,19 @@ func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { } resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if !headerIfNoneMatch || !matched { - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, `W/"13-1831710635"`, resp.Header.Get(fiber.HeaderETag)) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, `W/"13-1831710635"`, resp.Header.Get(fiber.HeaderETag)) return } if matched { - utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode) + require.Equal(t, fiber.StatusNotModified, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 0, len(b)) + require.NoError(t, err) + require.Equal(t, 0, len(b)) } } @@ -208,19 +208,19 @@ func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { } resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if !headerIfNoneMatch || !matched { - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, `"custom"`, resp.Header.Get(fiber.HeaderETag)) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, `"custom"`, resp.Header.Get(fiber.HeaderETag)) return } if matched { - utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode) + require.Equal(t, fiber.StatusNotModified, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 0, len(b)) + require.NoError(t, err) + require.Equal(t, 0, len(b)) } } @@ -241,8 +241,8 @@ func Test_ETag_CustomEtagPut(t *testing.T) { req := httptest.NewRequest("PUT", "/", nil) req.Header.Set(fiber.HeaderIfMatch, `"non-match"`) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusPreconditionFailed, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusPreconditionFailed, resp.StatusCode) } // go test -v -run=^$ -bench=Benchmark_Etag -benchmem -count=4 @@ -268,6 +268,6 @@ func Benchmark_Etag(b *testing.B) { h(fctx) } - utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode()) - utils.AssertEqual(b, `"13-1831710635"`, string(fctx.Response.Header.Peek(fiber.HeaderETag))) + require.Equal(b, 200, fctx.Response.Header.StatusCode()) + require.Equal(b, `"13-1831710635"`, string(fctx.Response.Header.Peek(fiber.HeaderETag))) } diff --git a/middleware/expvar/README.md b/middleware/expvar/README.md index 9d5f9329dd..b6f0a51b85 100644 --- a/middleware/expvar/README.md +++ b/middleware/expvar/README.md @@ -23,8 +23,8 @@ import ( "expvar" "fmt" - "github.com/gofiber/fiber/v2" - expvarmw "github.com/gofiber/fiber/v2/middleware/expvar" + "github.com/gofiber/fiber/v3" + expvarmw "github.com/gofiber/fiber/v3/middleware/expvar" ) var count = expvar.NewInt("count") diff --git a/middleware/expvar/expvar.go b/middleware/expvar/expvar.go index 3b4861ec64..8ea09e1a77 100644 --- a/middleware/expvar/expvar.go +++ b/middleware/expvar/expvar.go @@ -29,6 +29,6 @@ func New(config ...Config) fiber.Handler { return nil } - return c.Redirect("/debug/vars", 302) + return c.Redirect().To("/debug/vars") } } diff --git a/middleware/expvar/expvar_test.go b/middleware/expvar/expvar_test.go index 2bbc56e4f4..98e1a48560 100644 --- a/middleware/expvar/expvar_test.go +++ b/middleware/expvar/expvar_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func Test_Non_Expvar_Path(t *testing.T) { @@ -20,12 +20,12 @@ func Test_Non_Expvar_Path(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "escaped", string(b)) + require.NoError(t, err) + require.Equal(t, "escaped", string(b)) } func Test_Expvar_Index(t *testing.T) { @@ -38,14 +38,14 @@ func Test_Expvar_Index(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/vars", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, fiber.MIMEApplicationJSONCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, fiber.MIMEApplicationJSONCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, bytes.Contains(b, []byte("cmdline"))) - utils.AssertEqual(t, true, bytes.Contains(b, []byte("memstat"))) + require.NoError(t, err) + require.True(t, bytes.Contains(b, []byte("cmdline"))) + require.True(t, bytes.Contains(b, []byte("memstat"))) } func Test_Expvar_Filter(t *testing.T) { @@ -58,14 +58,14 @@ func Test_Expvar_Filter(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/vars?r=cmd", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, fiber.MIMEApplicationJSONCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, fiber.MIMEApplicationJSONCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, bytes.Contains(b, []byte("cmdline"))) - utils.AssertEqual(t, false, bytes.Contains(b, []byte("memstat"))) + require.NoError(t, err) + require.True(t, bytes.Contains(b, []byte("cmdline"))) + require.False(t, bytes.Contains(b, []byte("memstat"))) } func Test_Expvar_Other_Path(t *testing.T) { @@ -78,8 +78,8 @@ func Test_Expvar_Other_Path(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/vars/302", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 302, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 302, resp.StatusCode) } // go test -run Test_Expvar_Next @@ -95,6 +95,6 @@ func Test_Expvar_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/vars", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) } diff --git a/middleware/favicon/README.md b/middleware/favicon/README.md index 5796fc050b..8b74ba2b3b 100644 --- a/middleware/favicon/README.md +++ b/middleware/favicon/README.md @@ -25,8 +25,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/favicon" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/favicon" ) ``` diff --git a/middleware/favicon/favicon_test.go b/middleware/favicon/favicon_test.go index 9d9e1984ce..3db323cd7e 100644 --- a/middleware/favicon/favicon_test.go +++ b/middleware/favicon/favicon_test.go @@ -5,10 +5,10 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" ) // go test -run Test_Middleware_Favicon @@ -23,21 +23,21 @@ func Test_Middleware_Favicon(t *testing.T) { // Skip Favicon middleware resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusNoContent, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusNoContent, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest("OPTIONS", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest("PUT", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusMethodNotAllowed, resp.StatusCode, "Status code") - utils.AssertEqual(t, "GET, HEAD, OPTIONS", resp.Header.Get(fiber.HeaderAllow)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusMethodNotAllowed, resp.StatusCode, "Status code") + require.Equal(t, "GET, HEAD, OPTIONS", resp.Header.Get(fiber.HeaderAllow)) } // go test -run Test_Middleware_Favicon_Not_Found @@ -67,10 +67,10 @@ func Test_Middleware_Favicon_Found(t *testing.T) { resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) - utils.AssertEqual(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") + require.Equal(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) + require.Equal(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") } // go test -run Test_Middleware_Favicon_FileSystem @@ -83,10 +83,10 @@ func Test_Middleware_Favicon_FileSystem(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) - utils.AssertEqual(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") + require.Equal(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) + require.Equal(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") } // go test -run Test_Middleware_Favicon_CacheControl @@ -99,10 +99,10 @@ func Test_Middleware_Favicon_CacheControl(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") - utils.AssertEqual(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) - utils.AssertEqual(t, "public, max-age=100", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") + require.Equal(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType)) + require.Equal(t, "public, max-age=100", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control") } // go test -v -run=^$ -bench=Benchmark_Middleware_Favicon -benchmem -count=4 @@ -134,6 +134,6 @@ func Test_Favicon_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } diff --git a/middleware/filesystem/README.md b/middleware/filesystem/README.md index ea37671fe8..e7f3cbbb5e 100644 --- a/middleware/filesystem/README.md +++ b/middleware/filesystem/README.md @@ -12,11 +12,6 @@ Filesystem middleware for [Fiber](https://github.com/gofiber/fiber) that enables - [Examples](#examples) - [Config](#config) - [embed](#embed) - - [pkger](#pkger) - - [packr](#packr) - - [go.rice](#gorice) - - [fileb0x](#fileb0x) - - [statik](#statik) - [Config](#config-1) - [Default Config](#default-config) @@ -32,8 +27,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/filesystem" ) ``` @@ -44,12 +39,12 @@ Then create a Fiber app with `app := fiber.New()`. ```go // Provide a minimal config app.Use(filesystem.New(filesystem.Config{ - Root: http.Dir("./assets"), + Root: os.DirFS("./assets"), })) // Or extend your config for customization app.Use(filesystem.New(filesystem.Config{ - Root: http.Dir("./assets"), + Root: os.DirFS("./assets"), Browse: true, Index: "index.html", NotFoundFile: "404.html", @@ -72,8 +67,8 @@ import ( "log" "net/http" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/filesystem" ) // Embed a single file @@ -88,15 +83,14 @@ func main() { app := fiber.New() app.Use("/", filesystem.New(filesystem.Config{ - Root: http.FS(f), + Root: f, })) // Access file "image.png" under `static/` directory via URL: `http:///static/image.png`. // Without `PathPrefix`, you have to access it via URL: // `http:///static/static/image.png`. app.Use("/static", filesystem.New(filesystem.Config{ - Root: http.FS(embedDirStatic), - PathPrefix: "static", + Root: embedDirStatic, Browse: true, })) @@ -104,137 +98,6 @@ func main() { } ``` -### pkger - -[Pkger](https://github.com/markbates/pkger) can be used to embed files in a Golang excecutable. - -```go -package main - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" - - "github.com/markbates/pkger" -) - -func main() { - app := fiber.New() - - app.Use("/assets", filesystem.New(filesystem.Config{ - Root: pkger.Dir("/assets"), - }) - - log.Fatal(app.Listen(":3000")) -} -``` - -### packr - -[Packr](https://github.com/gobuffalo/packr) can be used to embed files in a Golang excecutable. - -```go -package main - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" - - "github.com/gobuffalo/packr/v2" -) - -func main() { - app := fiber.New() - - app.Use("/assets", filesystem.New(filesystem.Config{ - Root: packr.New("Assets Box", "/assets"), - }) - - log.Fatal(app.Listen(":3000")) -} -``` - -### go.rice - -https://github.com/GeertJohan/go.rice - -```go -package main - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" - - "github.com/GeertJohan/go.rice" -) - -func main() { - app := fiber.New() - - app.Use("/assets", filesystem.New(filesystem.Config{ - Root: rice.MustFindBox("assets").HTTPBox(), - }) - - log.Fatal(app.Listen(":3000")) -} -``` - -### fileb0x - -[Fileb0x](https://github.com/UnnoTed/fileb0x) can be used to embed files in a Golang excecutable. - -```go -package main - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" - - "/myEmbeddedFiles" -) - -func main() { - app := fiber.New() - - app.Use("/assets", filesystem.New(filesystem.Config{ - Root: myEmbeddedFiles.HTTP, - }) - - log.Fatal(app.Listen(":3000")) -} -``` - -### statik - -[Statik](https://github.com/rakyll/statik) can be used to embed files in a Golang excecutable. - -```go -package main - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" - - "/statik" - fs "github.com/rakyll/statik/fs" -) - -func main() { - statik, err := fs.New() - if err != nil { - panic(err) - } - - app := fiber.New() - - app.Use("/", filesystem.New(filesystem.Config{ - Root: statikFS, - }) - - log.Fatal(app.Listen(":3000")) -} -``` - ## Config ```go @@ -249,14 +112,12 @@ type Config struct { // to a collection of files and directories. // // Required. Default: nil - Root http.FileSystem `json:"-"` + Root fs.FS `json:"-"` // PathPrefix defines a prefix to be added to a filepath when // reading a file from the FileSystem. // - // Use when using Go 1.16 embed.FS - // - // Optional. Default "" + // Optional. Default "." PathPrefix string `json:"path_prefix"` // Enable directory browsing. @@ -288,7 +149,7 @@ type Config struct { var ConfigDefault = Config{ Next: nil, Root: nil, - PathPrefix: "", + PathPrefix: ".", Browse: false, Index: "/index.html", MaxAge: 0, diff --git a/middleware/filesystem/filesystem.go b/middleware/filesystem/filesystem.go index f769577f49..c8f08b636f 100644 --- a/middleware/filesystem/filesystem.go +++ b/middleware/filesystem/filesystem.go @@ -1,14 +1,15 @@ package filesystem import ( + "io/fs" "net/http" "os" + "path/filepath" "strconv" "strings" "sync" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" ) // Config defines the config for middleware. @@ -22,14 +23,12 @@ type Config struct { // to a collection of files and directories. // // Required. Default: nil - Root http.FileSystem `json:"-"` + Root fs.FS `json:"-"` // PathPrefix defines a prefix to be added to a filepath when // reading a file from the FileSystem. // - // Use when using Go 1.16 embed.FS - // - // Optional. Default "" + // Optional. Default "." PathPrefix string `json:"path_prefix"` // Enable directory browsing. @@ -42,6 +41,11 @@ type Config struct { // Optional. Default: "index.html" Index string `json:"index"` + // When set to true, enables direct download for files. + // + // Optional. Default: false. + Download bool `json:"download"` + // The value for the Cache-Control HTTP-header // that is set on the file response. MaxAge is defined in seconds. // @@ -58,7 +62,7 @@ type Config struct { var ConfigDefault = Config{ Next: nil, Root: nil, - PathPrefix: "", + PathPrefix: ".", Browse: false, Index: "/index.html", MaxAge: 0, @@ -77,6 +81,9 @@ func New(config ...Config) fiber.Handler { if cfg.Index == "" { cfg.Index = ConfigDefault.Index } + if cfg.PathPrefix == "" { + cfg.PathPrefix = ConfigDefault.PathPrefix + } if !strings.HasPrefix(cfg.Index, "/") { cfg.Index = "/" + cfg.Index } @@ -89,8 +96,13 @@ func New(config ...Config) fiber.Handler { panic("filesystem: Root cannot be nil") } - if cfg.PathPrefix != "" && !strings.HasPrefix(cfg.PathPrefix, "/") { - cfg.PathPrefix = "/" + cfg.PathPrefix + // PathPrefix configurations for io/fs compatibility. + if cfg.PathPrefix != "." && !strings.HasPrefix(cfg.PathPrefix, "/") { + cfg.PathPrefix = "./" + cfg.PathPrefix + } + + if cfg.NotFoundFile != "" { + cfg.NotFoundFile = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+cfg.NotFoundFile)) } var once sync.Once @@ -121,23 +133,26 @@ func New(config ...Config) fiber.Handler { if !strings.HasPrefix(path, "/") { path = "/" + path } - // Add PathPrefix - if cfg.PathPrefix != "" { - // PathPrefix already has a "/" prefix - path = cfg.PathPrefix + path - } var ( - file http.File + file fs.File stat os.FileInfo ) + // Add PathPrefix + if cfg.PathPrefix != "" { + // PathPrefix already has a "/" prefix + path = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+path)) + } + if len(path) > 1 { - path = utils.TrimRight(path, '/') + path = strings.TrimRight(path, "/") } - file, err = cfg.Root.Open(path) + + file, err = openFile(cfg.Root, path) + if err != nil && os.IsNotExist(err) && cfg.NotFoundFile != "" { - file, err = cfg.Root.Open(cfg.NotFoundFile) + file, err = openFile(cfg.Root, cfg.NotFoundFile) } if err != nil { @@ -153,8 +168,10 @@ func New(config ...Config) fiber.Handler { // Serve index if path is directory if stat.IsDir() { - indexPath := utils.TrimRight(path, '/') + cfg.Index - index, err := cfg.Root.Open(indexPath) + indexPath := strings.TrimRight(path, "/") + cfg.Index + indexPath = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+indexPath)) + + index, err := openFile(cfg.Root, indexPath) if err == nil { indexStat, err := index.Stat() if err == nil { @@ -169,6 +186,7 @@ func New(config ...Config) fiber.Handler { if cfg.Browse { return dirList(c, file) } + return fiber.ErrForbidden } @@ -183,6 +201,11 @@ func New(config ...Config) fiber.Handler { c.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat)) } + // Sets the response Content-Disposition header to attachment if the Download option is true and if it's a file + if cfg.Download && !stat.IsDir() { + c.Attachment() + } + if method == fiber.MethodGet { if cfg.MaxAge > 0 { c.Set(fiber.HeaderCacheControl, cacheControlStr) @@ -206,13 +229,15 @@ func New(config ...Config) fiber.Handler { } // SendFile ... -func SendFile(c fiber.Ctx, fs http.FileSystem, path string) (err error) { +func SendFile(c fiber.Ctx, filesystem fs.FS, path string) (err error) { var ( - file http.File + file fs.File stat os.FileInfo ) - file, err = fs.Open(path) + path = filepath.Join(".", filepath.Clean("/"+path)) + + file, err = openFile(filesystem, path) if err != nil { if os.IsNotExist(err) { return fiber.ErrNotFound @@ -226,8 +251,8 @@ func SendFile(c fiber.Ctx, fs http.FileSystem, path string) (err error) { // Serve index if path is directory if stat.IsDir() { - indexPath := utils.TrimRight(path, '/') + ConfigDefault.Index - index, err := fs.Open(indexPath) + indexPath := strings.TrimRight(path, "/") + ConfigDefault.Index + index, err := openFile(filesystem, indexPath) if err == nil { indexStat, err := index.Stat() if err == nil { diff --git a/middleware/filesystem/filesystem_test.go b/middleware/filesystem/filesystem_test.go index 6cae378734..dd2e8c0fbd 100644 --- a/middleware/filesystem/filesystem_test.go +++ b/middleware/filesystem/filesystem_test.go @@ -3,10 +3,11 @@ package filesystem import ( "net/http" "net/http/httptest" + "os" "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) // go test -run Test_FileSystem @@ -14,11 +15,11 @@ func Test_FileSystem(t *testing.T) { app := fiber.New() app.Use("/test", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), })) app.Use("/dir", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), Browse: true, })) @@ -27,13 +28,13 @@ func Test_FileSystem(t *testing.T) { }) app.Use("/spatest", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), Index: "index.html", NotFoundFile: "index.html", })) app.Use("/prefix", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), PathPrefix: "img", })) @@ -118,12 +119,12 @@ func Test_FileSystem(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, err := app.Test(httptest.NewRequest("GET", tt.url, nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, tt.statusCode, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, tt.statusCode, resp.StatusCode) if tt.contentType != "" { ct := resp.Header.Get("Content-Type") - utils.AssertEqual(t, tt.contentType, ct) + require.Equal(t, tt.contentType, ct) } }) } @@ -133,45 +134,61 @@ func Test_FileSystem(t *testing.T) { func Test_FileSystem_Next(t *testing.T) { app := fiber.New() app.Use(New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), Next: func(_ fiber.Ctx) bool { return true }, })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) +} + +// go test -run Test_FileSystem_Download +func Test_FileSystem_Download(t *testing.T) { + app := fiber.New() + app.Use(New(Config{ + Root: os.DirFS("../../.github/testdata/fs"), + Download: true, + })) + + resp, err := app.Test(httptest.NewRequest("GET", "/img/fiber.png", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.False(t, resp.Header.Get(fiber.HeaderContentLength) == "") + require.Equal(t, "image/png", resp.Header.Get(fiber.HeaderContentType)) + require.Equal(t, "attachment", resp.Header.Get(fiber.HeaderContentDisposition)) } func Test_FileSystem_NonGetAndHead(t *testing.T) { app := fiber.New() app.Use("/test", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), })) resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) } func Test_FileSystem_Head(t *testing.T) { app := fiber.New() app.Use("/test", New(Config{ - Root: http.Dir("../../.github/testdata/fs"), + Root: os.DirFS("../../.github/testdata/fs"), })) req, _ := http.NewRequest(fiber.MethodHead, "/test", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) } func Test_FileSystem_NoRoot(t *testing.T) { defer func() { - utils.AssertEqual(t, "filesystem: Root cannot be nil", recover()) + require.Equal(t, "filesystem: Root cannot be nil", recover()) }() app := fiber.New() @@ -183,24 +200,24 @@ func Test_FileSystem_UsingParam(t *testing.T) { app := fiber.New() app.Use("/:path", func(c fiber.Ctx) error { - return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html") + return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html") }) req, _ := http.NewRequest(fiber.MethodHead, "/index", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) } func Test_FileSystem_UsingParam_NonFile(t *testing.T) { app := fiber.New() app.Use("/:path", func(c fiber.Ctx) error { - return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html") + return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html") }) req, _ := http.NewRequest(fiber.MethodHead, "/template", nil) resp, err := app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) } diff --git a/middleware/filesystem/utils.go b/middleware/filesystem/utils.go index 9988d4264a..3561870a0a 100644 --- a/middleware/filesystem/utils.go +++ b/middleware/filesystem/utils.go @@ -3,14 +3,13 @@ package filesystem import ( "fmt" "html" - "net/http" - "os" + "io/fs" "path" + "path/filepath" "sort" "strings" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" ) func getFileExtension(path string) string { @@ -21,17 +20,23 @@ func getFileExtension(path string) string { return path[n:] } -func dirList(c fiber.Ctx, f http.File) error { - fileinfos, err := f.Readdir(-1) +func dirList(c fiber.Ctx, f fs.File) error { + ff := f.(fs.ReadDirFile) + fileinfos, err := ff.ReadDir(-1) if err != nil { return err } - fm := make(map[string]os.FileInfo, len(fileinfos)) + fm := make(map[string]fs.FileInfo, len(fileinfos)) filenames := make([]string, 0, len(fileinfos)) for _, fi := range fileinfos { name := fi.Name() - fm[name] = fi + info, err := fi.Info() + if err != nil { + return err + } + + fm[name] = info filenames = append(filenames, name) } @@ -41,7 +46,7 @@ func dirList(c fiber.Ctx, f http.File) error { fmt.Fprint(c, "
    ") if len(basePathEscaped) > 1 { - parentPathEscaped := html.EscapeString(utils.TrimRight(c.Path(), '/') + "/..") + parentPathEscaped := html.EscapeString(strings.TrimRight(c.Path(), "/") + "/..") fmt.Fprintf(c, `
  • ..
  • `, parentPathEscaped) } @@ -64,3 +69,9 @@ func dirList(c fiber.Ctx, f http.File) error { return nil } + +func openFile(fs fs.FS, name string) (fs.File, error) { + name = filepath.ToSlash(name) + + return fs.Open(name) +} diff --git a/middleware/helmet/README.md b/middleware/helmet/README.md index 587c923f9b..7cd99d0aef 100644 --- a/middleware/helmet/README.md +++ b/middleware/helmet/README.md @@ -8,7 +8,7 @@ ### Install ``` -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/helmet/v2 ``` ### Example @@ -16,7 +16,7 @@ go get -u github.com/gofiber/helmet/v2 package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/helmet/v2" ) diff --git a/middleware/helmet/helmet_test.go b/middleware/helmet/helmet_test.go index 56df76f6f6..0b13b5a0d9 100644 --- a/middleware/helmet/helmet_test.go +++ b/middleware/helmet/helmet_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func Test_Default(t *testing.T) { @@ -22,13 +22,13 @@ func Test_Default(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "1; mode=block", resp.Header.Get(fiber.HeaderXXSSProtection)) - utils.AssertEqual(t, "nosniff", resp.Header.Get(fiber.HeaderXContentTypeOptions)) - utils.AssertEqual(t, "SAMEORIGIN", resp.Header.Get(fiber.HeaderXFrameOptions)) - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderReferrerPolicy)) - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderPermissionsPolicy)) + require.NoError(t, err) + require.Equal(t, "1; mode=block", resp.Header.Get(fiber.HeaderXXSSProtection)) + require.Equal(t, "nosniff", resp.Header.Get(fiber.HeaderXContentTypeOptions)) + require.Equal(t, "SAMEORIGIN", resp.Header.Get(fiber.HeaderXFrameOptions)) + require.Equal(t, "", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) + require.Equal(t, "", resp.Header.Get(fiber.HeaderReferrerPolicy)) + require.Equal(t, "", resp.Header.Get(fiber.HeaderPermissionsPolicy)) } func Test_Filter(t *testing.T) { @@ -49,12 +49,12 @@ func Test_Filter(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "no-referrer", resp.Header.Get(fiber.HeaderReferrerPolicy)) + require.NoError(t, err) + require.Equal(t, "no-referrer", resp.Header.Get(fiber.HeaderReferrerPolicy)) resp, err = app.Test(httptest.NewRequest("GET", "/filter", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderReferrerPolicy)) + require.NoError(t, err) + require.Equal(t, "", resp.Header.Get(fiber.HeaderReferrerPolicy)) } func Test_ContentSecurityPolicy(t *testing.T) { @@ -69,8 +69,8 @@ func Test_ContentSecurityPolicy(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "default-src 'none'", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) + require.NoError(t, err) + require.Equal(t, "default-src 'none'", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) } func Test_ContentSecurityPolicyReportOnly(t *testing.T) { @@ -86,9 +86,9 @@ func Test_ContentSecurityPolicyReportOnly(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "default-src 'none'", resp.Header.Get(fiber.HeaderContentSecurityPolicyReportOnly)) - utils.AssertEqual(t, "", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) + require.NoError(t, err) + require.Equal(t, "default-src 'none'", resp.Header.Get(fiber.HeaderContentSecurityPolicyReportOnly)) + require.Equal(t, "", resp.Header.Get(fiber.HeaderContentSecurityPolicy)) } func Test_PermissionsPolicy(t *testing.T) { @@ -103,6 +103,6 @@ func Test_PermissionsPolicy(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "microphone=()", resp.Header.Get(fiber.HeaderPermissionsPolicy)) + require.NoError(t, err) + require.Equal(t, "microphone=()", resp.Header.Get(fiber.HeaderPermissionsPolicy)) } diff --git a/middleware/keyauth/README.md b/middleware/keyauth/README.md index 311755918e..2e604d8271 100644 --- a/middleware/keyauth/README.md +++ b/middleware/keyauth/README.md @@ -10,7 +10,7 @@ Special thanks to [József Sallai](https://github.com/jozsefsallai) & [Ray Mayem ### Install ``` -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/keyauth/v2 ``` ### Example @@ -18,7 +18,7 @@ go get -u github.com/gofiber/keyauth/v2 package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/keyauth/v2" ) diff --git a/middleware/limiter/README.md b/middleware/limiter/README.md index 3091b36c58..919764bf14 100644 --- a/middleware/limiter/README.md +++ b/middleware/limiter/README.md @@ -30,8 +30,8 @@ First import the middleware from Fiber, ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/limiter" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/limiter" ) ``` @@ -95,7 +95,7 @@ type Config struct { // Default: func(c fiber.Ctx) string { // return c.IP() // } - KeyGenerator func(*fiber.Ctx) string + KeyGenerator func(fiber.Ctx) string // Expiration is the time on how long to keep records of requests in memory // diff --git a/middleware/limiter/limiter_fixed.go b/middleware/limiter/limiter_fixed.go index 57842d3e61..9948afc884 100644 --- a/middleware/limiter/limiter_fixed.go +++ b/middleware/limiter/limiter_fixed.go @@ -4,9 +4,9 @@ import ( "strconv" "sync" "sync/atomic" - "time" "github.com/gofiber/fiber/v3" + "github.com/gofiber/utils" ) type FixedWindow struct{} @@ -17,7 +17,6 @@ func (FixedWindow) New(cfg Config) fiber.Handler { // Limiter variables mux = &sync.RWMutex{} max = strconv.Itoa(cfg.Max) - timestamp = uint64(time.Now().Unix()) expiration = uint64(cfg.Expiration.Seconds()) ) @@ -25,12 +24,7 @@ func (FixedWindow) New(cfg Config) fiber.Handler { manager := newManager(cfg.Storage) // Update timestamp every second - go func() { - for { - atomic.StoreUint64(×tamp, uint64(time.Now().Unix())) - time.Sleep(1 * time.Second) - } - }() + utils.StartTimeStampUpdater() // Return new handler return func(c fiber.Ctx) error { @@ -49,7 +43,7 @@ func (FixedWindow) New(cfg Config) fiber.Handler { e := manager.get(key) // Get timestamp - ts := atomic.LoadUint64(×tamp) + ts := uint64(atomic.LoadUint32(&utils.Timestamp)) // Set expiration if entry does not exist if e.exp == 0 { diff --git a/middleware/limiter/limiter_sliding.go b/middleware/limiter/limiter_sliding.go index d471b02006..5fc1d730e3 100644 --- a/middleware/limiter/limiter_sliding.go +++ b/middleware/limiter/limiter_sliding.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gofiber/fiber/v3" + "github.com/gofiber/utils" ) type SlidingWindow struct{} @@ -17,7 +18,6 @@ func (SlidingWindow) New(cfg Config) fiber.Handler { // Limiter variables mux = &sync.RWMutex{} max = strconv.Itoa(cfg.Max) - timestamp = uint64(time.Now().Unix()) expiration = uint64(cfg.Expiration.Seconds()) ) @@ -25,12 +25,7 @@ func (SlidingWindow) New(cfg Config) fiber.Handler { manager := newManager(cfg.Storage) // Update timestamp every second - go func() { - for { - atomic.StoreUint64(×tamp, uint64(time.Now().Unix())) - time.Sleep(1 * time.Second) - } - }() + utils.StartTimeStampUpdater() // Return new handler return func(c fiber.Ctx) error { @@ -49,7 +44,7 @@ func (SlidingWindow) New(cfg Config) fiber.Handler { e := manager.get(key) // Get timestamp - ts := atomic.LoadUint64(×tamp) + ts := uint64(atomic.LoadUint32(&utils.Timestamp)) // Set expiration if entry does not exist if e.exp == 0 { diff --git a/middleware/limiter/limiter_test.go b/middleware/limiter/limiter_test.go index 02ddd2bdd9..9cae095c11 100644 --- a/middleware/limiter/limiter_test.go +++ b/middleware/limiter/limiter_test.go @@ -10,7 +10,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/storage/memory" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -34,12 +34,12 @@ func Test_Limiter_Concurrency_Store(t *testing.T) { singleRequest := func(wg *sync.WaitGroup) { defer wg.Done() resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "Hello tester!", string(body)) + require.NoError(t, err) + require.Equal(t, "Hello tester!", string(body)) } for i := 0; i <= 49; i++ { @@ -50,14 +50,14 @@ func Test_Limiter_Concurrency_Store(t *testing.T) { wg.Wait() resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_Concurrency -race -v @@ -79,12 +79,12 @@ func Test_Limiter_Concurrency(t *testing.T) { singleRequest := func(wg *sync.WaitGroup) { defer wg.Done() resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "Hello tester!", string(body)) + require.NoError(t, err) + require.Equal(t, "Hello tester!", string(body)) } for i := 0; i <= 49; i++ { @@ -95,14 +95,14 @@ func Test_Limiter_Concurrency(t *testing.T) { wg.Wait() resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_No_Skip_Choices -v @@ -124,16 +124,16 @@ func Test_Limiter_No_Skip_Choices(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 400, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 400, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) } // go test -run Test_Limiter_Skip_Failed_Requests -v @@ -154,22 +154,22 @@ func Test_Limiter_Skip_Failed_Requests(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 400, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 400, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_Skip_Successful_Requests -v @@ -192,22 +192,22 @@ func Test_Limiter_Skip_Successful_Requests(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 400, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 400, resp.StatusCode) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 400, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 400, resp.StatusCode) } // go test -v -run=^$ -bench=Benchmark_Limiter_Custom_Store -benchmem -count=4 @@ -247,8 +247,8 @@ func Test_Limiter_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_Limiter_Headers(t *testing.T) { @@ -269,7 +269,7 @@ func Test_Limiter_Headers(t *testing.T) { app.Handler()(fctx) - utils.AssertEqual(t, "50", string(fctx.Response.Header.Peek("X-RateLimit-Limit"))) + require.Equal(t, "50", string(fctx.Response.Header.Peek("X-RateLimit-Limit"))) if v := string(fctx.Response.Header.Peek("X-RateLimit-Remaining")); v == "" { t.Errorf("The X-RateLimit-Remaining header is not set correctly - value is an empty string.") } @@ -321,11 +321,11 @@ func Test_Sliding_Window(t *testing.T) { singleRequest := func(shouldFail bool) { resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) if shouldFail { - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 429, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 429, resp.StatusCode) } else { - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) } } diff --git a/middleware/limiter/manager.go b/middleware/limiter/manager.go index bbf8d6e4e7..79609baafe 100644 --- a/middleware/limiter/manager.go +++ b/middleware/limiter/manager.go @@ -5,11 +5,13 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/internal/storage/memory" + "github.com/gofiber/fiber/v3/internal/memory" ) // go:generate msgp // msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported +// don't forget to replace the msgp import path to: +// "github.com/gofiber/fiber/v2/internal/msgp" type item struct { currHits int prevHits int @@ -19,6 +21,7 @@ type item struct { //msgp:ignore manager type manager struct { pool sync.Pool + memory *memory.Storage storage fiber.Storage } @@ -26,20 +29,18 @@ func newManager(storage fiber.Storage) *manager { // Create new storage handler manager := &manager{ pool: sync.Pool{ - New: func() any { + New: func() interface{} { return new(item) }, }, } - if storage != nil { // Use provided storage if provided manager.storage = storage } else { - // Fallback to memory storage - manager.storage = memory.New(1) + // Fallback too memory storage + manager.memory = memory.New() } - return manager } @@ -58,39 +59,58 @@ func (m *manager) release(e *item) { // get data from storage or memory func (m *manager) get(key string) (it *item) { - it = m.acquire() - if raw, _ := m.storage.Get(key); raw != nil { - if _, err := it.UnmarshalMsg(raw); err != nil { - return + if m.storage != nil { + it = m.acquire() + if raw, _ := m.storage.Get(key); raw != nil { + if _, err := it.UnmarshalMsg(raw); err != nil { + return + } } + return + } + if it, _ = m.memory.Get(key).(*item); it == nil { + it = m.acquire() } return - } // get raw data from storage or memory func (m *manager) getRaw(key string) (raw []byte) { - raw, _ = m.storage.Get(key) - + if m.storage != nil { + raw, _ = m.storage.Get(key) + } else { + raw, _ = m.memory.Get(key).([]byte) + } return } // set data to storage or memory func (m *manager) set(key string, it *item, exp time.Duration) { - if raw, err := it.MarshalMsg(nil); err == nil { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + if raw, err := it.MarshalMsg(nil); err == nil { + _ = m.storage.Set(key, raw, exp) + } + // we can release data because it's serialized to database + m.release(it) + } else { + m.memory.Set(key, it, exp) } - - // we can release data because it's serialized to database - m.release(it) } // set data to storage or memory func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { - _ = m.storage.Set(key, raw, exp) + if m.storage != nil { + _ = m.storage.Set(key, raw, exp) + } else { + m.memory.Set(key, raw, exp) + } } // delete data from storage or memory func (m *manager) delete(key string) { - _ = m.storage.Delete(key) + if m.storage != nil { + _ = m.storage.Delete(key) + } else { + m.memory.Delete(key) + } } diff --git a/middleware/logger/README.md b/middleware/logger/README.md index c388b2c665..c77d4f6afb 100644 --- a/middleware/logger/README.md +++ b/middleware/logger/README.md @@ -11,6 +11,7 @@ Logger middleware for [Fiber](https://github.com/gofiber/fiber) that logs HTTP r - [Logging Request ID](#logging-request-id) - [Changing TimeZone & TimeFormat](#changing-timezone--timeformat) - [Custom File Writer](#custom-file-writer) + - [Logging with Zerolog](#logging-with-zerolog) - [Config](#config) - [Default Config](#default-config-1) - [Constants](#constants) @@ -24,8 +25,8 @@ func New(config ...Config) fiber.Handler First ensure the appropriate packages are imported ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" ) ``` @@ -76,6 +77,43 @@ app.Use(logger.New(logger.Config{ })) ``` +### Logging with Zerolog +```go +package main + +import ( + "os" + + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + app := fiber.New() + + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + + app.Use(logger.New(logger.Config{LoggerFunc: func(c fiber.Ctx, data *logger.LoggerData, cfg logger.Config) error { + log.Info(). + Str("path", c.Path()). + Str("method", c.Method()). + Int("status", c.Response(). + StatusCode()). + Msg("new request") + + return nil + }})) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("test") + }) + + app.Listen(":3000") +} +``` + ## Config ```go // Config defines the config for middleware. @@ -109,19 +147,38 @@ type Config struct { // // Default: os.Stderr Output io.Writer + + // You can define specific things before the returning the handler: colors, template, etc. + // + // Optional. Default: beforeHandlerFunc + BeforeHandlerFunc func(Config) + + // You can use custom loggers with Fiber by using this field. + // This field is really useful if you're using Zerolog, Zap, Logrus, apex/log etc. + // If you don't define anything for this field, it'll use classical logger of Fiber. + // + // Optional. Default: defaultLogger + LoggerFunc func(c fiber.Ctx, data *LoggerData, cfg Config) error } ``` ## Default Config ```go +// ConfigDefault is the default config var ConfigDefault = Config{ - Next: nil, - Format: "[${time}] ${status} - ${latency} ${method} ${path}\n", - TimeFormat: "15:04:05", - TimeZone: "Local", - TimeInterval: 500 * time.Millisecond, - Output: os.Stderr, + Next: nil, + Format: defaultFormat, + TimeFormat: "15:04:05", + TimeZone: "Local", + TimeInterval: 500 * time.Millisecond, + Output: os.Stdout, + BeforeHandlerFunc: beforeHandlerFunc, + LoggerFunc: defaultLogger, + enableColors: true, } + +// default logging format for Fiber's default logger +var defaultFormat = "[${time}] ${status} - ${latency} ${method} ${path}\n" ``` ## Constants diff --git a/middleware/logger/config.go b/middleware/logger/config.go index b8e6d4df9d..700f2c6942 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -41,6 +41,18 @@ type Config struct { // Default: os.Stdout Output io.Writer + // You can define specific things before the returning the handler: colors, template, etc. + // + // Optional. Default: beforeHandlerFunc + BeforeHandlerFunc func(Config) + + // You can use custom loggers with Fiber by using this field. + // This field is really useful if you're using Zerolog, Zap, Logrus, apex/log etc. + // If you don't define anything for this field, it'll use default logger of Fiber. + // + // Optional. Default: defaultLogger + LoggerFunc func(c fiber.Ctx, data *LoggerData, cfg Config) error + enableColors bool enableLatency bool timeZoneLocation *time.Location @@ -48,15 +60,20 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ - Next: nil, - Format: "[${time}] ${status} - ${latency} ${method} ${path}\n", - TimeFormat: "15:04:05", - TimeZone: "Local", - TimeInterval: 500 * time.Millisecond, - Output: os.Stdout, - enableColors: true, + Next: nil, + Format: defaultFormat, + TimeFormat: "15:04:05", + TimeZone: "Local", + TimeInterval: 500 * time.Millisecond, + Output: os.Stdout, + BeforeHandlerFunc: beforeHandlerFunc, + LoggerFunc: defaultLogger, + enableColors: true, } +// default logging format for Fiber's default logger +var defaultFormat = "[${time}] ${status} - ${latency} ${method} ${path}\n" + // Function to check if the logger format is compatible for coloring func validCustomFormat(format string) bool { validTemplates := []string{"${status}", "${method}"} @@ -105,5 +122,14 @@ func configDefault(config ...Config) Config { if cfg.Output == nil { cfg.Output = ConfigDefault.Output } + + if cfg.BeforeHandlerFunc == nil { + cfg.BeforeHandlerFunc = ConfigDefault.BeforeHandlerFunc + } + + if cfg.LoggerFunc == nil { + cfg.LoggerFunc = ConfigDefault.LoggerFunc + } + return cfg } diff --git a/middleware/logger/default_logger.go b/middleware/logger/default_logger.go new file mode 100644 index 0000000000..7565f3dc5e --- /dev/null +++ b/middleware/logger/default_logger.go @@ -0,0 +1,221 @@ +package logger + +import ( + "fmt" + "io" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/gofiber/fiber/v3" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "github.com/valyala/bytebufferpool" + "github.com/valyala/fasthttp" + "github.com/valyala/fasttemplate" +) + +// LoggerData is a struct to define some variables to use in custom logger function. +type LoggerData struct { + mu sync.Mutex + Pid string + ErrPaddingStr string + ChainErr error + Start time.Time + Stop time.Time + Timestamp atomic.Value +} + +var tmpl *fasttemplate.Template + +// default logger for fiber +func defaultLogger(c fiber.Ctx, data *LoggerData, cfg Config) error { + // Alias colors + colors := c.App().Config().ColorScheme + + // Get new buffer + buf := bytebufferpool.Get() + + // Default output when no custom Format or io.Writer is given + if cfg.enableColors && cfg.Format == defaultFormat { + // Format error if exist + formatErr := "" + if data.ChainErr != nil { + formatErr = colors.Red + " | " + data.ChainErr.Error() + colors.Reset + } + + // Format log to buffer + _, _ = buf.WriteString(fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+data.ErrPaddingStr+"s %s\n", + data.Timestamp.Load().(string), + statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset, + data.Stop.Sub(data.Start).Round(time.Millisecond), + c.IP(), + methodColor(c.Method(), colors), c.Method(), colors.Reset, + c.Path(), + formatErr, + )) + + // Write buffer to output + _, _ = cfg.Output.Write(buf.Bytes()) + + // Put buffer back to pool + bytebufferpool.Put(buf) + + // End chain + return nil + } + + // Loop over template tags to replace it with the correct value + _, err := tmpl.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { + switch tag { + case TagTime: + return buf.WriteString(data.Timestamp.Load().(string)) + case TagReferer: + return buf.WriteString(c.Get(fiber.HeaderReferer)) + case TagProtocol: + return buf.WriteString(c.Protocol()) + case TagScheme: + return buf.WriteString(c.Scheme()) + case TagPid: + return buf.WriteString(data.Pid) + case TagPort: + return buf.WriteString(c.Port()) + case TagIP: + return buf.WriteString(c.IP()) + case TagIPs: + return buf.WriteString(c.Get(fiber.HeaderXForwardedFor)) + case TagHost: + return buf.WriteString(c.Host()) + case TagPath: + return buf.WriteString(c.Path()) + case TagURL: + return buf.WriteString(c.OriginalURL()) + case TagUA: + return buf.WriteString(c.Get(fiber.HeaderUserAgent)) + case TagLatency: + return buf.WriteString(fmt.Sprintf("%7v", data.Stop.Sub(data.Start).Round(time.Millisecond))) + case TagBody: + return buf.Write(c.Body()) + case TagBytesReceived: + return appendInt(buf, len(c.Request().Body())) + case TagBytesSent: + return appendInt(buf, len(c.Response().Body())) + case TagRoute: + return buf.WriteString(c.Route().Path) + case TagStatus: + if cfg.enableColors { + return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset)) + } + return appendInt(buf, c.Response().StatusCode()) + case TagResBody: + return buf.Write(c.Response().Body()) + case TagReqHeaders: + out := make(map[string]string, 0) + if err := c.Bind().Header(&out); err != nil { + return 0, err + } + + reqHeaders := make([]string, 0) + for k, v := range out { + reqHeaders = append(reqHeaders, k+"="+v) + } + return buf.Write([]byte(strings.Join(reqHeaders, "&"))) + case TagQueryStringParams: + return buf.WriteString(c.Request().URI().QueryArgs().String()) + case TagMethod: + if cfg.enableColors { + return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset)) + } + return buf.WriteString(c.Method()) + case TagBlack: + return buf.WriteString(colors.Black) + case TagRed: + return buf.WriteString(colors.Red) + case TagGreen: + return buf.WriteString(colors.Green) + case TagYellow: + return buf.WriteString(colors.Yellow) + case TagBlue: + return buf.WriteString(colors.Blue) + case TagMagenta: + return buf.WriteString(colors.Magenta) + case TagCyan: + return buf.WriteString(colors.Cyan) + case TagWhite: + return buf.WriteString(colors.White) + case TagReset: + return buf.WriteString(colors.Reset) + case TagError: + if data.ChainErr != nil { + return buf.WriteString(data.ChainErr.Error()) + } + return buf.WriteString("-") + default: + // Check if we have a value tag i.e.: "reqHeader:x-key" + switch { + case strings.HasPrefix(tag, TagReqHeader): + return buf.WriteString(c.Get(tag[10:])) + case strings.HasPrefix(tag, TagRespHeader): + return buf.WriteString(c.GetRespHeader(tag[11:])) + case strings.HasPrefix(tag, TagQuery): + return buf.WriteString(c.Query(tag[6:])) + case strings.HasPrefix(tag, TagForm): + return buf.WriteString(c.FormValue(tag[5:])) + case strings.HasPrefix(tag, TagCookie): + return buf.WriteString(c.Cookies(tag[7:])) + case strings.HasPrefix(tag, TagLocals): + switch v := c.Locals(tag[7:]).(type) { + case []byte: + return buf.Write(v) + case string: + return buf.WriteString(v) + case nil: + return 0, nil + default: + return buf.WriteString(fmt.Sprintf("%v", v)) + } + } + } + return 0, nil + }) + // Also write errors to the buffer + if err != nil { + _, _ = buf.WriteString(err.Error()) + } + data.mu.Lock() + // Write buffer to output + if _, err := cfg.Output.Write(buf.Bytes()); err != nil { + // Write error to output + if _, err := cfg.Output.Write([]byte(err.Error())); err != nil { + // There is something wrong with the given io.Writer + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } + } + data.mu.Unlock() + // Put buffer back to pool + bytebufferpool.Put(buf) + + return nil +} + +// run something before returning the handler +func beforeHandlerFunc(cfg Config) { + // Create template parser + tmpl = fasttemplate.New(cfg.Format, "${", "}") + + // If colors are enabled, check terminal compatibility + if cfg.enableColors { + cfg.Output = colorable.NewColorableStdout() + if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { + cfg.Output = colorable.NewNonColorable(os.Stdout) + } + } +} + +func appendInt(buf *bytebufferpool.ByteBuffer, v int) (int, error) { + old := len(buf.B) + buf.B = fasthttp.AppendUint(buf.B, v) + return len(buf.B) - old, nil +} diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 8049081c1d..2bd55dc709 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -1,8 +1,6 @@ package logger import ( - "fmt" - "io" "os" "strconv" "strings" @@ -11,11 +9,6 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" - "github.com/valyala/bytebufferpool" - "github.com/valyala/fasthttp" - "github.com/valyala/fasttemplate" ) // Logger variables @@ -60,19 +53,6 @@ const ( TagReset = "reset" ) -// Color values -const ( - cBlack = "\u001b[90m" - cRed = "\u001b[91m" - cGreen = "\u001b[92m" - cYellow = "\u001b[93m" - cBlue = "\u001b[94m" - cMagenta = "\u001b[95m" - cCyan = "\u001b[96m" - cWhite = "\u001b[97m" - cReset = "\u001b[0m" -) - // New creates a new middleware handler func New(config ...Config) fiber.Handler { // Set default config @@ -89,14 +69,11 @@ func New(config ...Config) fiber.Handler { // Check if format contains latency cfg.enableLatency = strings.Contains(cfg.Format, "${latency}") - // Create template parser - tmpl := fasttemplate.New(cfg.Format, "${", "}") - // Create correct timeformat var timestamp atomic.Value timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat)) - // Update date/time every 750 milliseconds in a separate go routine + // Update date/time every 500 milliseconds in a separate go routine if strings.Contains(cfg.Format, "${time}") { go func() { for { @@ -111,20 +88,25 @@ func New(config ...Config) fiber.Handler { // Set variables var ( - once sync.Once mu sync.Mutex + once sync.Once errHandler fiber.ErrorHandler ) - // If colors are enabled, check terminal compatibility - if cfg.enableColors { - cfg.Output = colorable.NewColorableStdout() - if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { - cfg.Output = colorable.NewNonColorable(os.Stdout) - } - } + // Err padding errPadding := 15 errPaddingStr := strconv.Itoa(errPadding) + + // Before handling func + cfg.BeforeHandlerFunc(cfg) + + // Logger data + data := &LoggerData{ + Pid: pid, + ErrPaddingStr: errPaddingStr, + Timestamp: timestamp, + } + // Return new handler return func(c fiber.Ctx) (err error) { // Don't execute middleware if Next returns true @@ -170,174 +152,17 @@ func New(config ...Config) fiber.Handler { stop = time.Now() } - // Get new buffer - buf := bytebufferpool.Get() - - // Default output when no custom Format or io.Writer is given - if cfg.enableColors && cfg.Format == ConfigDefault.Format { - // Format error if exist - formatErr := "" - if chainErr != nil { - formatErr = cRed + " | " + chainErr.Error() + cReset - } - - // Format log to buffer - _, _ = buf.WriteString(fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+errPaddingStr+"s %s\n", - timestamp.Load().(string), - statusColor(c.Response().StatusCode()), c.Response().StatusCode(), cReset, - stop.Sub(start).Round(time.Millisecond), - c.IP(), - methodColor(c.Method()), c.Method(), cReset, - c.Path(), - formatErr, - )) - - // Write buffer to output - _, _ = cfg.Output.Write(buf.Bytes()) - - // Put buffer back to pool - bytebufferpool.Put(buf) - - // End chain - return nil - } - - // Loop over template tags to replace it with the correct value - _, err = tmpl.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { - switch tag { - case TagTime: - return buf.WriteString(timestamp.Load().(string)) - case TagReferer: - return buf.WriteString(c.Get(fiber.HeaderReferer)) - case TagProtocol: - return buf.WriteString(c.Protocol()) - case TagScheme: - return buf.WriteString(c.Scheme()) - case TagPid: - return buf.WriteString(pid) - case TagPort: - return buf.WriteString(c.Port()) - case TagIP: - return buf.WriteString(c.IP()) - case TagIPs: - return buf.WriteString(c.Get(fiber.HeaderXForwardedFor)) - case TagHost: - return buf.WriteString(c.Hostname()) - case TagPath: - return buf.WriteString(c.Path()) - case TagURL: - return buf.WriteString(c.OriginalURL()) - case TagUA: - return buf.WriteString(c.Get(fiber.HeaderUserAgent)) - case TagLatency: - return buf.WriteString(stop.Sub(start).String()) - case TagBody: - return buf.Write(c.Body()) - case TagBytesReceived: - return appendInt(buf, len(c.Request().Body())) - case TagBytesSent: - return appendInt(buf, len(c.Response().Body())) - case TagRoute: - return buf.WriteString(c.Route().Path) - case TagStatus: - if cfg.enableColors { - return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode()), c.Response().StatusCode(), cReset)) - } - return appendInt(buf, c.Response().StatusCode()) - case TagResBody: - return buf.Write(c.Response().Body()) - case TagReqHeaders: - out := make(map[string]string, 0) - if err := c.Bind().Header(&out); err != nil { - return 0, err - } - - reqHeaders := make([]string, 0) - for k, v := range out { - reqHeaders = append(reqHeaders, k+"="+v) - } - return buf.Write([]byte(strings.Join(reqHeaders, "&"))) - case TagQueryStringParams: - return buf.WriteString(c.Request().URI().QueryArgs().String()) - case TagMethod: - if cfg.enableColors { - return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method()), c.Method(), cReset)) - } - return buf.WriteString(c.Method()) - case TagBlack: - return buf.WriteString(cBlack) - case TagRed: - return buf.WriteString(cRed) - case TagGreen: - return buf.WriteString(cGreen) - case TagYellow: - return buf.WriteString(cYellow) - case TagBlue: - return buf.WriteString(cBlue) - case TagMagenta: - return buf.WriteString(cMagenta) - case TagCyan: - return buf.WriteString(cCyan) - case TagWhite: - return buf.WriteString(cWhite) - case TagReset: - return buf.WriteString(cReset) - case TagError: - if chainErr != nil { - return buf.WriteString(chainErr.Error()) - } - return buf.WriteString("-") - default: - // Check if we have a value tag i.e.: "reqHeader:x-key" - switch { - case strings.HasPrefix(tag, TagReqHeader): - return buf.WriteString(c.Get(tag[10:])) - case strings.HasPrefix(tag, TagRespHeader): - return buf.WriteString(c.GetRespHeader(tag[11:])) - case strings.HasPrefix(tag, TagQuery): - return buf.WriteString(c.Query(tag[6:])) - case strings.HasPrefix(tag, TagForm): - return buf.WriteString(c.FormValue(tag[5:])) - case strings.HasPrefix(tag, TagCookie): - return buf.WriteString(c.Cookies(tag[7:])) - case strings.HasPrefix(tag, TagLocals): - switch v := c.Locals(tag[7:]).(type) { - case []byte: - return buf.Write(v) - case string: - return buf.WriteString(v) - case nil: - return 0, nil - default: - return buf.WriteString(fmt.Sprintf("%v", v)) - } - } - } - return 0, nil - }) - // Also write errors to the buffer - if err != nil { - _, _ = buf.WriteString(err.Error()) - } + // Logger instance & update some logger data fields mu.Lock() - // Write buffer to output - if _, err := cfg.Output.Write(buf.Bytes()); err != nil { - // Write error to output - if _, err := cfg.Output.Write([]byte(err.Error())); err != nil { - // There is something wrong with the given io.Writer - fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) - } + data.ChainErr = chainErr + data.Start = start + data.Stop = stop + + if err = cfg.LoggerFunc(c, data, cfg); err != nil { + return err } mu.Unlock() - // Put buffer back to pool - bytebufferpool.Put(buf) return nil } } - -func appendInt(buf *bytebufferpool.ByteBuffer, v int) (int, error) { - old := len(buf.B) - buf.B = fasthttp.AppendUint(buf.B, v) - return len(buf.B) - old, nil -} diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 9a93528881..243c80c6d7 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -12,7 +12,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/requestid" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" ) @@ -34,9 +34,9 @@ func Test_Logger(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusInternalServerError, resp.StatusCode) - utils.AssertEqual(t, "some random error", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) + require.Equal(t, "some random error", buf.String()) } // go test -run Test_Logger_locals @@ -66,23 +66,23 @@ func Test_Logger_locals(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "johndoe", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "johndoe", buf.String()) buf.Reset() resp, err = app.Test(httptest.NewRequest("GET", "/int", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "55", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "55", buf.String()) buf.Reset() resp, err = app.Test(httptest.NewRequest("GET", "/empty", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "", buf.String()) } // go test -run Test_Logger_Next @@ -95,8 +95,8 @@ func Test_Logger_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_Logger_ErrorTimeZone @@ -107,8 +107,8 @@ func Test_Logger_ErrorTimeZone(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } type fakeOutput int @@ -127,10 +127,10 @@ func Test_Logger_ErrorOutput(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) - utils.AssertEqual(t, 2, int(*o)) + require.Equal(t, 2, int(*o)) } // go test -run Test_Logger_All @@ -144,12 +144,15 @@ func Test_Logger_All(t *testing.T) { Output: buf, })) + // Alias colors + colors := app.Config().ColorScheme + resp, err := app.Test(httptest.NewRequest("GET", "/?foo=bar", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) - expected := fmt.Sprintf("%dHost=example.comhttpHTTP/1.10.0.0.0example.com/?foo=bar/%s%s%s%s%s%s%s%s%sCannot GET /", os.Getpid(), cBlack, cRed, cGreen, cYellow, cBlue, cMagenta, cCyan, cWhite, cReset) - utils.AssertEqual(t, expected, buf.String()) + expected := fmt.Sprintf("%dHost=example.comhttpHTTP/1.10.0.0.0example.com/?foo=bar/%s%s%s%s%s%s%s%s%sCannot GET /", os.Getpid(), colors.Black, colors.Red, colors.Green, colors.Yellow, colors.Blue, colors.Magenta, colors.Cyan, colors.White, colors.Reset) + require.Equal(t, expected, buf.String()) } // go test -run Test_Query_Params @@ -164,11 +167,11 @@ func Test_Query_Params(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/?foo=bar&baz=moz", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) expected := "foo=bar&baz=moz" - utils.AssertEqual(t, expected, buf.String()) + require.Equal(t, expected, buf.String()) } // go test -run Test_Response_Body @@ -191,18 +194,18 @@ func Test_Response_Body(t *testing.T) { }) _, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) expectedGetResponse := "Sample response body" - utils.AssertEqual(t, expectedGetResponse, buf.String()) + require.Equal(t, expectedGetResponse, buf.String()) buf.Reset() // Reset buffer to test POST _, err = app.Test(httptest.NewRequest("POST", "/test", nil)) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) expectedPostResponse := "Post in test" - utils.AssertEqual(t, expectedPostResponse, buf.String()) + require.Equal(t, expectedPostResponse, buf.String()) } // go test -run Test_Logger_AppendUint @@ -222,9 +225,9 @@ func Test_Logger_AppendUint(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "0 5 200", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "0 5 200", buf.String()) } // go test -run Test_Logger_Data_Race -race @@ -253,10 +256,10 @@ func Test_Logger_Data_Race(t *testing.T) { resp2, err2 = app.Test(httptest.NewRequest("GET", "/", nil)) wg.Wait() - utils.AssertEqual(t, nil, err1) - utils.AssertEqual(t, fiber.StatusOK, resp1.StatusCode) - utils.AssertEqual(t, nil, err2) - utils.AssertEqual(t, fiber.StatusOK, resp2.StatusCode) + require.Nil(t, err1) + require.Equal(t, fiber.StatusOK, resp1.StatusCode) + require.Nil(t, err2) + require.Equal(t, fiber.StatusOK, resp2.StatusCode) } // go test -v -run=^$ -bench=Benchmark_Logger -benchmem -count=4 @@ -284,7 +287,7 @@ func Benchmark_Logger(b *testing.B) { h(fctx) } - utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode()) + require.Equal(b, 200, fctx.Response.Header.StatusCode()) } // go test -run Test_Response_Header @@ -309,9 +312,9 @@ func Test_Response_Header(t *testing.T) { resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "Hello fiber!", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "Hello fiber!", buf.String()) } // go test -run Test_Req_Header @@ -331,9 +334,9 @@ func Test_Req_Header(t *testing.T) { headerReq.Header.Add("test", "Hello fiber!") resp, err := app.Test(headerReq) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "Hello fiber!", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "Hello fiber!", buf.String()) } // go test -run Test_ReqHeader_Header @@ -353,7 +356,7 @@ func Test_ReqHeader_Header(t *testing.T) { reqHeaderReq.Header.Add("test", "Hello fiber!") resp, err := app.Test(reqHeaderReq) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, "Hello fiber!", buf.String()) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "Hello fiber!", buf.String()) } diff --git a/middleware/logger/utils.go b/middleware/logger/utils.go index 99dc60f225..5c0432718c 100644 --- a/middleware/logger/utils.go +++ b/middleware/logger/utils.go @@ -4,36 +4,36 @@ import ( "github.com/gofiber/fiber/v3" ) -func methodColor(method string) string { +func methodColor(method string, colors fiber.Colors) string { switch method { case fiber.MethodGet: - return cCyan + return colors.Cyan case fiber.MethodPost: - return cGreen + return colors.Green case fiber.MethodPut: - return cYellow + return colors.Yellow case fiber.MethodDelete: - return cRed + return colors.Red case fiber.MethodPatch: - return cWhite + return colors.White case fiber.MethodHead: - return cMagenta + return colors.Magenta case fiber.MethodOptions: - return cBlue + return colors.Blue default: - return cReset + return colors.Reset } } -func statusColor(code int) string { +func statusColor(code int, colors fiber.Colors) string { switch { case code >= fiber.StatusOK && code < fiber.StatusMultipleChoices: - return cGreen + return colors.Green case code >= fiber.StatusMultipleChoices && code < fiber.StatusBadRequest: - return cBlue + return colors.Blue case code >= fiber.StatusBadRequest && code < fiber.StatusInternalServerError: - return cYellow + return colors.Yellow default: - return cRed + return colors.Red } } diff --git a/middleware/pprof/README.md b/middleware/pprof/README.md index 351e6ae7e4..4e576e9a98 100644 --- a/middleware/pprof/README.md +++ b/middleware/pprof/README.md @@ -13,8 +13,8 @@ func New() fiber.Handler Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/pprof" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/pprof" ) ``` diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go index e3a5ea104d..afaab4407e 100644 --- a/middleware/pprof/pprof.go +++ b/middleware/pprof/pprof.go @@ -72,7 +72,7 @@ func New(config ...Config) fiber.Handler { path = "/debug/pprof/" } - return c.Redirect(path, fiber.StatusFound) + return c.Redirect().To(path) } return nil } diff --git a/middleware/pprof/pprof_test.go b/middleware/pprof/pprof_test.go index 4305cf2999..eac8b3caec 100644 --- a/middleware/pprof/pprof_test.go +++ b/middleware/pprof/pprof_test.go @@ -7,11 +7,11 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func Test_Non_Pprof_Path(t *testing.T) { - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Use(New()) @@ -20,16 +20,16 @@ func Test_Non_Pprof_Path(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "escaped", string(b)) + require.NoError(t, err) + require.Equal(t, "escaped", string(b)) } func Test_Pprof_Index(t *testing.T) { - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Use(New()) @@ -38,17 +38,17 @@ func Test_Pprof_Index(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/pprof/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) - utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, bytes.Contains(b, []byte("/debug/pprof/"))) + require.NoError(t, err) + require.True(t, bytes.Contains(b, []byte("/debug/pprof/"))) } func Test_Pprof_Subs(t *testing.T) { - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Use(New()) @@ -68,14 +68,14 @@ func Test_Pprof_Subs(t *testing.T) { target += "?seconds=1" } resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, target, nil), 5000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 200, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) }) } } func Test_Pprof_Other(t *testing.T) { - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Use(New()) @@ -84,8 +84,8 @@ func Test_Pprof_Other(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/pprof/302", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 302, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 302, resp.StatusCode) } // go test -run Test_Pprof_Next @@ -101,6 +101,6 @@ func Test_Pprof_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/pprof/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 404, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) } diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md index c7ed607768..daa66245fc 100644 --- a/middleware/proxy/README.md +++ b/middleware/proxy/README.md @@ -13,8 +13,8 @@ Proxy middleware for [Fiber](https://github.com/gofiber/fiber) that allows you t ```go func Balancer(config Config) fiber.Handler -func Forward(addr string) fiber.Handler -func Do(c fiber.Ctx, addr string) error +func Forward(addr string, clients ...*fasthttp.Client) fiber.Handler +func Do(c fiber.Ctx, addr string, clients ...*fasthttp.Client) error ``` ### Examples @@ -23,8 +23,8 @@ Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/proxy" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/proxy" ) ``` @@ -37,9 +37,21 @@ proxy.WithTlsConfig(&tls.Config{ InsecureSkipVerify: true, }) +// if you need to use global self-custom client, you should use proxy.WithClient. +proxy.WithClient(&fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, +}) + // Forward to url app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif")) +// Forward to url with local custom client +app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif", &fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, +})) + // Make request within handler app.Get("/:id", func(c fiber.Ctx) error { url := "https://i.imgur.com/"+c.Params("id")+".gif" @@ -120,8 +132,13 @@ type Config struct { // Per-connection buffer size for responses' writing. WriteBufferSize int - // tls config for the http client - TlsConfig *tls.Config + // tls config for the http client. + TlsConfig *tls.Config + + // Client is custom client when client config is complex. + // Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig + // will not be used if the client are set. + Client *fasthttp.LBClient } ``` diff --git a/middleware/proxy/config.go b/middleware/proxy/config.go index d06bd4e84c..0a482c6a55 100644 --- a/middleware/proxy/config.go +++ b/middleware/proxy/config.go @@ -47,8 +47,13 @@ type Config struct { // Per-connection buffer size for responses' writing. WriteBufferSize int - // tls config for the http client + // tls config for the http client. TlsConfig *tls.Config + + // Client is custom client when client config is complex. + // Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig + // will not be used if the client are set. + Client *fasthttp.LBClient } // ConfigDefault is the default config @@ -75,7 +80,7 @@ func configDefault(config ...Config) Config { } // Set default values - if len(cfg.Servers) == 0 { + if len(cfg.Servers) == 0 && cfg.Client == nil { panic("Servers cannot be empty") } return cfg diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 37468eaaa0..00285e6e8e 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -5,9 +5,10 @@ import ( "crypto/tls" "net/url" "strings" + "sync" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) @@ -17,34 +18,39 @@ func Balancer(config Config) fiber.Handler { cfg := configDefault(config) // Load balanced client - var lbc fasthttp.LBClient - // Set timeout - lbc.Timeout = cfg.Timeout - - // Scheme must be provided, falls back to http - // TODO add https support - for _, server := range cfg.Servers { - if !strings.HasPrefix(server, "http") { - server = "http://" + server - } + var lbc = &fasthttp.LBClient{} + // Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig + // will not be used if the client are set. + if config.Client == nil { + // Set timeout + lbc.Timeout = cfg.Timeout + // Scheme must be provided, falls back to http + for _, server := range cfg.Servers { + if !strings.HasPrefix(server, "http") { + server = "http://" + server + } - u, err := url.Parse(server) - if err != nil { - panic(err) - } + u, err := url.Parse(server) + if err != nil { + panic(err) + } - client := &fasthttp.HostClient{ - NoDefaultUserAgentHeader: true, - DisablePathNormalizing: true, - Addr: u.Host, + client := &fasthttp.HostClient{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, + Addr: u.Host, - ReadBufferSize: config.ReadBufferSize, - WriteBufferSize: config.WriteBufferSize, + ReadBufferSize: config.ReadBufferSize, + WriteBufferSize: config.WriteBufferSize, - TLSConfig: config.TlsConfig, - } + TLSConfig: config.TlsConfig, + } - lbc.Clients = append(lbc.Clients, client) + lbc.Clients = append(lbc.Clients, client) + } + } else { + // Set custom client + lbc = config.Client } // Return new handler @@ -90,42 +96,65 @@ func Balancer(config Config) fiber.Handler { } } -var client = fasthttp.Client{ +var client = &fasthttp.Client{ NoDefaultUserAgentHeader: true, DisablePathNormalizing: true, } +var lock sync.RWMutex + // WithTlsConfig update http client with a user specified tls.config // This function should be called before Do and Forward. +// Deprecated: use WithClient instead. func WithTlsConfig(tlsConfig *tls.Config) { client.TLSConfig = tlsConfig } +// WithClient sets the global proxy client. +// This function should be called before Do and Forward. +func WithClient(cli *fasthttp.Client) { + lock.Lock() + defer lock.Unlock() + client = cli +} + // Forward performs the given http request and fills the given http response. // This method will return an fiber.Handler -func Forward(addr string) fiber.Handler { +func Forward(addr string, clients ...*fasthttp.Client) fiber.Handler { return func(c fiber.Ctx) error { - return Do(c, addr) + return Do(c, addr, clients...) } } // Do performs the given http request and fills the given http response. // This method can be used within a fiber.Handler -func Do(c fiber.Ctx, addr string) error { +func Do(c fiber.Ctx, addr string, clients ...*fasthttp.Client) error { + var cli *fasthttp.Client + if len(clients) != 0 { + // Set local client + cli = clients[0] + } else { + // Set global client + lock.RLock() + cli = client + lock.RUnlock() + } req := c.Request() res := c.Response() originalURL := utils.CopyString(c.OriginalURL()) defer req.SetRequestURI(originalURL) - req.SetRequestURI(addr) + + copiedURL := utils.CopyString(addr) + req.SetRequestURI(copiedURL) // NOTE: if req.isTLS is true, SetRequestURI keeps the scheme as https. // issue reference: // https://github.com/gofiber/fiber/issues/1762 - if scheme := getScheme(utils.UnsafeBytes(addr)); len(scheme) > 0 { + if scheme := getScheme(utils.UnsafeBytes(copiedURL)); len(scheme) > 0 { req.URI().SetSchemeBytes(scheme) } req.Header.Del(fiber.HeaderConnection) - if err := client.Do(req, res); err != nil { + if err := cli.Do(req, res); err != nil { return err } res.Header.Del(fiber.HeaderConnection) diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index ebc1b4eb01..e0b3c429af 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "io" "net" + "net/http" "net/http/httptest" "strings" "testing" @@ -11,24 +12,29 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/tlstest" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" ) func createProxyTestServer(handler fiber.Handler, t *testing.T) (*fiber.App, string) { t.Helper() - target := fiber.New(fiber.Config{DisableStartupMessage: true}) + target := fiber.New() target.Get("/", handler) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) + + addr := ln.Addr().String() go func() { - utils.AssertEqual(t, nil, target.Listener(ln)) + require.Nil(t, target.Listener(ln, fiber.ListenConfig{ + DisableStartupMessage: true, + })) }() time.Sleep(2 * time.Second) - addr := ln.Addr().String() return target, addr } @@ -39,7 +45,7 @@ func Test_Proxy_Empty_Upstream_Servers(t *testing.T) { defer func() { if r := recover(); r != nil { - utils.AssertEqual(t, "Servers cannot be empty", r) + require.Equal(t, "Servers cannot be empty", r) } }() app := fiber.New() @@ -59,8 +65,8 @@ func Test_Proxy_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_Proxy @@ -72,18 +78,18 @@ func Test_Proxy(t *testing.T) { ) resp, err := target.Test(httptest.NewRequest("GET", "/", nil), 2000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusTeapot, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Use(Balancer(Config{Servers: []string{addr}})) req := httptest.NewRequest("GET", "/", nil) req.Host = addr resp, err = app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusTeapot, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } // go test -run Test_Proxy_Balancer_WithTlsConfig @@ -91,14 +97,14 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) { t.Parallel() serverTLSConf, _, err := tlstest.GetTLSConfigs() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Get("/tlsbalaner", func(c fiber.Ctx) error { return c.SendString("tls balancer") @@ -113,47 +119,55 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) { TlsConfig: clientTLSConf, })) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, fiber.ListenConfig{ + DisableStartupMessage: true, + })) + }() code, body, errs := fiber.Get("https://" + addr + "/tlsbalaner").TLSConfig(clientTLSConf).String() - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, fiber.StatusOK, code) - utils.AssertEqual(t, "tls balancer", body) + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "tls balancer", body) } // go test -run Test_Proxy_Forward_WithTlsConfig_To_Http func Test_Proxy_Forward_WithTlsConfig_To_Http(t *testing.T) { - t.Parallel() + //t.Parallel() _, targetAddr := createProxyTestServer(func(c fiber.Ctx) error { return c.SendString("hello from target") }, t) proxyServerTLSConf, _, err := tlstest.GetTLSConfigs() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) proxyServerLn, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) proxyServerLn = tls.NewListener(proxyServerLn, proxyServerTLSConf) - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() proxyAddr := proxyServerLn.Addr().String() app.Use(Forward("http://" + targetAddr)) - go func() { utils.AssertEqual(t, nil, app.Listener(proxyServerLn)) }() + go func() { + require.Nil(t, app.Listener(proxyServerLn, fiber.ListenConfig{ + DisableStartupMessage: true, + })) + }() code, body, errs := fiber.Get("https://" + proxyAddr). InsecureSkipVerify(). Timeout(5 * time.Second). String() - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, fiber.StatusOK, code) - utils.AssertEqual(t, "hello from target", body) + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "hello from target", body) } // go test -run Test_Proxy_Forward @@ -169,12 +183,12 @@ func Test_Proxy_Forward(t *testing.T) { app.Use(Forward("http://" + addr)) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "forwarded", string(b)) + require.NoError(t, err) + require.Equal(t, "forwarded", string(b)) } // go test -run Test_Proxy_Forward_WithTlsConfig @@ -182,14 +196,14 @@ func Test_Proxy_Forward_WithTlsConfig(t *testing.T) { t.Parallel() serverTLSConf, _, err := tlstest.GetTLSConfigs() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) - app := fiber.New(fiber.Config{DisableStartupMessage: true}) + app := fiber.New() app.Get("/tlsfwd", func(c fiber.Ctx) error { return c.SendString("tls forward") @@ -202,13 +216,17 @@ func Test_Proxy_Forward_WithTlsConfig(t *testing.T) { WithTlsConfig(clientTLSConf) app.Use(Forward("https://" + addr + "/tlsfwd")) - go func() { utils.AssertEqual(t, nil, app.Listener(ln)) }() + go func() { + require.Nil(t, app.Listener(ln, fiber.ListenConfig{ + DisableStartupMessage: true, + })) + }() code, body, errs := fiber.Get("https://" + addr).TLSConfig(clientTLSConf).String() - utils.AssertEqual(t, 0, len(errs)) - utils.AssertEqual(t, fiber.StatusOK, code) - utils.AssertEqual(t, "tls forward", body) + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "tls forward", body) } // go test -run Test_Proxy_Modify_Response @@ -229,12 +247,12 @@ func Test_Proxy_Modify_Response(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "modified response", string(b)) + require.NoError(t, err) + require.Equal(t, "modified response", string(b)) } // go test -run Test_Proxy_Modify_Request @@ -256,12 +274,12 @@ func Test_Proxy_Modify_Request(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "modified request", string(b)) + require.NoError(t, err) + require.Equal(t, "modified request", string(b)) } // go test -run Test_Proxy_Timeout_Slow_Server @@ -280,12 +298,12 @@ func Test_Proxy_Timeout_Slow_Server(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil), 5000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "fiber is awesome", string(b)) + require.NoError(t, err) + require.Equal(t, "fiber is awesome", string(b)) } // go test -run Test_Proxy_With_Timeout @@ -304,12 +322,12 @@ func Test_Proxy_With_Timeout(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil), 2000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusInternalServerError, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) b, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "timeout", string(b)) + require.NoError(t, err) + require.Equal(t, "timeout", string(b)) } // go test -run Test_Proxy_Buffer_Size_Response @@ -326,8 +344,8 @@ func Test_Proxy_Buffer_Size_Response(t *testing.T) { app.Use(Balancer(Config{Servers: []string{addr}})) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusInternalServerError, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) app = fiber.New() app.Use(Balancer(Config{ @@ -336,8 +354,8 @@ func Test_Proxy_Buffer_Size_Response(t *testing.T) { })) resp, err = app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) } // go test -race -run Test_Proxy_Do_RestoreOriginalURL @@ -352,13 +370,131 @@ func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) { if err := Do(c, "/proxy"); err != nil { return err } - utils.AssertEqual(t, originalURL, c.OriginalURL()) + require.Equal(t, originalURL, c.OriginalURL()) return c.SendString("ok") }) _, err1 := app.Test(httptest.NewRequest("GET", "/test", nil)) // This test requires multiple requests due to zero allocation used in fiber _, err2 := app.Test(httptest.NewRequest("GET", "/test", nil)) - utils.AssertEqual(t, nil, err1) - utils.AssertEqual(t, nil, err2) + require.Nil(t, err1) + require.Nil(t, err2) +} + +// go test -race -run Test_Proxy_Do_HTTP_Prefix_URL +func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) { + t.Parallel() + + _, addr := createProxyTestServer(func(c fiber.Ctx) error { + return c.SendString("hello world") + }, t) + + app := fiber.New() + app.Get("/*", func(c fiber.Ctx) error { + path := c.OriginalURL() + url := strings.TrimPrefix(path, "/") + + require.Equal(t, "http://"+addr, url) + if err := Do(c, url); err != nil { + return err + } + c.Response().Header.Del(fiber.HeaderServer) + return nil + }) + + resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/http://"+addr, nil)) + require.NoError(t, err) + s, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "hello world", string(s)) +} + +// go test -race -run Test_Proxy_Forward_Global_Client +func Test_Proxy_Forward_Global_Client(t *testing.T) { + t.Parallel() + ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") + require.NoError(t, err) + WithClient(&fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, + }) + app := fiber.New() + app.Get("/test_global_client", func(c fiber.Ctx) error { + return c.SendString("test_global_client") + }) + + addr := ln.Addr().String() + app.Use(Forward("http://" + addr + "/test_global_client")) + go func() { + require.Nil(t, app.Listener(ln, fiber.ListenConfig{ + DisableStartupMessage: true, + })) + }() + + code, body, errs := fiber.Get("http://" + addr).String() + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "test_global_client", body) +} + +// go test -race -run Test_Proxy_Forward_Local_Client +func Test_Proxy_Forward_Local_Client(t *testing.T) { + t.Parallel() + ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") + require.NoError(t, err) + app := fiber.New() + app.Get("/test_local_client", func(c fiber.Ctx) error { + return c.SendString("test_local_client") + }) + + addr := ln.Addr().String() + app.Use(Forward("http://"+addr+"/test_local_client", &fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, + Dial: func(addr string) (net.Conn, error) { + return fasthttp.Dial(addr) + }, + })) + go func() { + require.Nil(t, app.Listener(ln, fiber.ListenConfig{ + DisableStartupMessage: true, + })) + }() + + code, body, errs := fiber.Get("http://" + addr).String() + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "test_local_client", body) +} + +// go test -run Test_ProxyBalancer_Custom_Client +func Test_ProxyBalancer_Custom_Client(t *testing.T) { + t.Parallel() + + target, addr := createProxyTestServer( + func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusTeapot) }, t, + ) + + resp, err := target.Test(httptest.NewRequest("GET", "/", nil), 2000) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) + + app := fiber.New() + + app.Use(Balancer(Config{Client: &fasthttp.LBClient{ + Clients: []fasthttp.BalancingClient{ + &fasthttp.HostClient{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, + Addr: addr, + }, + }, + Timeout: time.Second, + }})) + + req := httptest.NewRequest("GET", "/", nil) + req.Host = addr + resp, err = app.Test(req) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } diff --git a/middleware/recover/README.md b/middleware/recover/README.md index 659af897d9..991b755bef 100644 --- a/middleware/recover/README.md +++ b/middleware/recover/README.md @@ -17,8 +17,8 @@ func New(config ...Config) fiber.Handler Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) ``` @@ -50,7 +50,7 @@ type Config struct { // StackTraceHandler defines a function to handle stack trace // // Optional. Default: defaultStackTraceHandler - StackTraceHandler func(c fiber.Ctx, e interface{}) + StackTraceHandler func(c fiber.Ctx, e any) } ``` diff --git a/middleware/recover/recover_test.go b/middleware/recover/recover_test.go index b7046e4247..b837a9d46d 100644 --- a/middleware/recover/recover_test.go +++ b/middleware/recover/recover_test.go @@ -5,14 +5,14 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) // go test -run Test_Recover func Test_Recover(t *testing.T) { app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { - utils.AssertEqual(t, "Hi, I'm an error!", err.Error()) + require.Equal(t, "Hi, I'm an error!", err.Error()) return c.SendStatus(fiber.StatusTeapot) }, }) @@ -24,8 +24,8 @@ func Test_Recover(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/panic", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusTeapot, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } // go test -run Test_Recover_Next @@ -38,8 +38,8 @@ func Test_Recover_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_Recover_EnableStackTrace(t *testing.T) { @@ -53,6 +53,6 @@ func Test_Recover_EnableStackTrace(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/panic", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusInternalServerError, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) } diff --git a/middleware/redirect/README.md b/middleware/redirect/README.md index 1d91dcf1a4..cc7acf201a 100644 --- a/middleware/redirect/README.md +++ b/middleware/redirect/README.md @@ -8,7 +8,7 @@ ### Install ``` -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/redirect/v2 ``` ### Example @@ -16,7 +16,7 @@ go get -u github.com/gofiber/redirect/v2 package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/redirect/v2" ) diff --git a/middleware/redirect/redirect.go b/middleware/redirect/redirect.go index 8473df3a79..0dec5263a9 100644 --- a/middleware/redirect/redirect.go +++ b/middleware/redirect/redirect.go @@ -61,7 +61,7 @@ func New(config ...Config) fiber.Handler { for k, v := range cfg.rulesRegex { replacer := captureTokens(k, c.Path()) if replacer != nil { - return c.Redirect(replacer.Replace(v), cfg.StatusCode) + return c.Redirect().Status(cfg.StatusCode).To(replacer.Replace(v)) } } return c.Next() diff --git a/middleware/requestid/README.md b/middleware/requestid/README.md index 031abf39c6..5ef9e6b30a 100644 --- a/middleware/requestid/README.md +++ b/middleware/requestid/README.md @@ -17,8 +17,8 @@ func New(config ...Config) fiber.Handler Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/requestid" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/requestid" ) ``` diff --git a/middleware/requestid/config.go b/middleware/requestid/config.go index 5c6930c30d..9909bee518 100644 --- a/middleware/requestid/config.go +++ b/middleware/requestid/config.go @@ -2,7 +2,7 @@ package requestid import ( "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // Config defines the config for middleware. diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go index 64237714f3..f011373515 100644 --- a/middleware/requestid/requestid_test.go +++ b/middleware/requestid/requestid_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) // go test -run Test_RequestID @@ -19,19 +19,19 @@ func Test_RequestID(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) reqid := resp.Header.Get(fiber.HeaderXRequestID) - utils.AssertEqual(t, 36, len(reqid)) + require.Equal(t, 36, len(reqid)) req := httptest.NewRequest("GET", "/", nil) req.Header.Add(fiber.HeaderXRequestID, reqid) resp, err = app.Test(req) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) - utils.AssertEqual(t, reqid, resp.Header.Get(fiber.HeaderXRequestID)) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, reqid, resp.Header.Get(fiber.HeaderXRequestID)) } // go test -run Test_RequestID_Next @@ -44,9 +44,9 @@ func Test_RequestID_Next(t *testing.T) { })) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, resp.Header.Get(fiber.HeaderXRequestID), "") - utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, resp.Header.Get(fiber.HeaderXRequestID), "") + require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_RequestID_Locals @@ -70,6 +70,6 @@ func Test_RequestID_Locals(t *testing.T) { }) _, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, reqId, ctxVal) + require.NoError(t, err) + require.Equal(t, reqId, ctxVal) } diff --git a/middleware/rewrite/README.md b/middleware/rewrite/README.md index ea8c494d59..cec9ec1c6a 100644 --- a/middleware/rewrite/README.md +++ b/middleware/rewrite/README.md @@ -8,7 +8,7 @@ ### Install ``` -go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/rewrite/v2 ``` ### Example @@ -16,7 +16,7 @@ go get -u github.com/gofiber/rewrite/v2 package main import ( - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v3" "github.com/gofiber/rewrite/v2" ) diff --git a/middleware/session/README.md b/middleware/session/README.md index 0bf03ed2d7..085e621cd1 100644 --- a/middleware/session/README.md +++ b/middleware/session/README.md @@ -19,12 +19,12 @@ _NOTE: This middleware uses our [Storage](https://github.com/gofiber/storage) pa ```go func New(config ...Config) *Store -func (s *Store) RegisterType(i interface{}) +func (s *Store) RegisterType(i any) func (s *Store) Get(c fiber.Ctx) (*Session, error) func (s *Store) Reset() error -func (s *Session) Get(key string) interface{} -func (s *Session) Set(key string, val interface{}) +func (s *Session) Get(key string) any +func (s *Session) Set(key string, val any) func (s *Session) Delete(key string) func (s *Session) Destroy() error func (s *Session) Regenerate() error @@ -35,14 +35,14 @@ func (s *Session) Keys() []string func (s *Session) SetExpiry(time.Duration) ``` -**⚠ _Storing `interface{}` values are limited to built-ins Go types_** +**⚠ _Storing `any` values are limited to built-ins Go types_** ### Examples Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/session" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/session" ) ``` diff --git a/middleware/session/config.go b/middleware/session/config.go index 6e68df130c..0da7ea6bd1 100644 --- a/middleware/session/config.go +++ b/middleware/session/config.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" ) // Config defines the config for middleware. diff --git a/middleware/session/data.go b/middleware/session/data.go index e92d63b18a..1f4337bb92 100644 --- a/middleware/session/data.go +++ b/middleware/session/data.go @@ -27,9 +27,7 @@ func acquireData() *data { func (d *data) Reset() { d.Lock() - for key := range d.Data { - delete(d.Data, key) - } + d.Data = make(map[string]interface{}) d.Unlock() } diff --git a/middleware/session/session.go b/middleware/session/session.go index 3cf02a54d9..987838c66a 100644 --- a/middleware/session/session.go +++ b/middleware/session/session.go @@ -7,14 +7,14 @@ import ( "time" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) type Session struct { id string // session id fresh bool // if new session - ctx fiber.Ctx // fiber context + ctx fiber.Ctx // fiber context config *Store // store configuration data *data // key value data byteBuffer *bytes.Buffer // byte buffer for the en- and decode @@ -144,10 +144,8 @@ func (s *Session) Save() error { s.exp = s.config.Expiration } - // Create session with the session ID if fresh - if s.fresh { - s.setSession() - } + // Update client cookie + s.setSession() // Convert data to bytes mux.Lock() diff --git a/middleware/session/session_test.go b/middleware/session/session_test.go index 9c0ea74973..891abb7f3c 100644 --- a/middleware/session/session_test.go +++ b/middleware/session/session_test.go @@ -6,7 +6,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/storage/memory" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -28,55 +28,55 @@ func Test_Session(t *testing.T) { // get session sess, err := store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, sess.Fresh()) + require.NoError(t, err) + require.True(t, sess.Fresh()) // get keys keys := sess.Keys() - utils.AssertEqual(t, []string{}, keys) + require.Equal(t, []string{}, keys) // get value name := sess.Get("name") - utils.AssertEqual(t, nil, name) + require.Nil(t, name) // set value sess.Set("name", "john") // get value name = sess.Get("name") - utils.AssertEqual(t, "john", name) + require.Equal(t, "john", name) keys = sess.Keys() - utils.AssertEqual(t, []string{"name"}, keys) + require.Equal(t, []string{"name"}, keys) // delete key sess.Delete("name") // get value name = sess.Get("name") - utils.AssertEqual(t, nil, name) + require.Nil(t, name) // get keys keys = sess.Keys() - utils.AssertEqual(t, []string{}, keys) + require.Equal(t, []string{}, keys) // get id id := sess.ID() - utils.AssertEqual(t, "123", id) + require.Equal(t, "123", id) // save the old session first err = sess.Save() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // requesting entirely new context to prevent falsy tests ctx = app.NewCtx(&fasthttp.RequestCtx{}) sess, err = store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, sess.Fresh()) + require.NoError(t, err) + require.True(t, sess.Fresh()) // this id should be randomly generated as session key was deleted - utils.AssertEqual(t, 36, len(sess.ID())) + require.Equal(t, 36, len(sess.ID())) // when we use the original session for the second time // the session be should be same if the session is not expired @@ -85,9 +85,9 @@ func Test_Session(t *testing.T) { // request the server with the old session ctx.Request().Header.SetCookie(store.sessionName, id) sess, err = store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, false, sess.Fresh()) - utils.AssertEqual(t, sess.id, id) + require.NoError(t, err) + require.False(t, sess.Fresh()) + require.Equal(t, sess.id, id) } // go test -run Test_Session_Types @@ -108,8 +108,8 @@ func Test_Session_Types(t *testing.T) { // get session sess, err := store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, true, sess.Fresh()) + require.NoError(t, err) + require.True(t, sess.Fresh()) // the session string is no longer be 123 newSessionIDString := sess.ID() @@ -123,9 +123,9 @@ func Test_Session_Types(t *testing.T) { Name: "John", } // set value - var vbool bool = true - var vstring string = "str" - var vint int = 13 + var vbool = true + var vstring = "str" + var vint = 13 var vint8 int8 = 13 var vint16 int16 = 13 var vint32 int32 = 13 @@ -166,34 +166,34 @@ func Test_Session_Types(t *testing.T) { // save session err = sess.Save() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // get session sess, err = store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, false, sess.Fresh()) + require.NoError(t, err) + require.False(t, sess.Fresh()) // get value - utils.AssertEqual(t, vuser, sess.Get("vuser").(User)) - utils.AssertEqual(t, vbool, sess.Get("vbool").(bool)) - utils.AssertEqual(t, vstring, sess.Get("vstring").(string)) - utils.AssertEqual(t, vint, sess.Get("vint").(int)) - utils.AssertEqual(t, vint8, sess.Get("vint8").(int8)) - utils.AssertEqual(t, vint16, sess.Get("vint16").(int16)) - utils.AssertEqual(t, vint32, sess.Get("vint32").(int32)) - utils.AssertEqual(t, vint64, sess.Get("vint64").(int64)) - utils.AssertEqual(t, vuint, sess.Get("vuint").(uint)) - utils.AssertEqual(t, vuint8, sess.Get("vuint8").(uint8)) - utils.AssertEqual(t, vuint16, sess.Get("vuint16").(uint16)) - utils.AssertEqual(t, vuint32, sess.Get("vuint32").(uint32)) - utils.AssertEqual(t, vuint64, sess.Get("vuint64").(uint64)) - utils.AssertEqual(t, vuintptr, sess.Get("vuintptr").(uintptr)) - utils.AssertEqual(t, vbyte, sess.Get("vbyte").(byte)) - utils.AssertEqual(t, vrune, sess.Get("vrune").(rune)) - utils.AssertEqual(t, vfloat32, sess.Get("vfloat32").(float32)) - utils.AssertEqual(t, vfloat64, sess.Get("vfloat64").(float64)) - utils.AssertEqual(t, vcomplex64, sess.Get("vcomplex64").(complex64)) - utils.AssertEqual(t, vcomplex128, sess.Get("vcomplex128").(complex128)) + require.Equal(t, vuser, sess.Get("vuser").(User)) + require.Equal(t, vbool, sess.Get("vbool").(bool)) + require.Equal(t, vstring, sess.Get("vstring").(string)) + require.Equal(t, vint, sess.Get("vint").(int)) + require.Equal(t, vint8, sess.Get("vint8").(int8)) + require.Equal(t, vint16, sess.Get("vint16").(int16)) + require.Equal(t, vint32, sess.Get("vint32").(int32)) + require.Equal(t, vint64, sess.Get("vint64").(int64)) + require.Equal(t, vuint, sess.Get("vuint").(uint)) + require.Equal(t, vuint8, sess.Get("vuint8").(uint8)) + require.Equal(t, vuint16, sess.Get("vuint16").(uint16)) + require.Equal(t, vuint32, sess.Get("vuint32").(uint32)) + require.Equal(t, vuint64, sess.Get("vuint64").(uint64)) + require.Equal(t, vuintptr, sess.Get("vuintptr").(uintptr)) + require.Equal(t, vbyte, sess.Get("vbyte").(byte)) + require.Equal(t, vrune, sess.Get("vrune").(rune)) + require.Equal(t, vfloat32, sess.Get("vfloat32").(float32)) + require.Equal(t, vfloat64, sess.Get("vfloat64").(float64)) + require.Equal(t, vcomplex64, sess.Get("vcomplex64").(complex64)) + require.Equal(t, vcomplex128, sess.Get("vcomplex128").(complex128)) } // go test -run Test_Session_Store_Reset @@ -209,19 +209,19 @@ func Test_Session_Store_Reset(t *testing.T) { // get session sess, _ := store.Get(ctx) // make sure its new - utils.AssertEqual(t, true, sess.Fresh()) + require.True(t, sess.Fresh()) // set value & save sess.Set("hello", "world") ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) - sess.Save() + require.NoError(t, sess.Save()) // reset store - store.Reset() + require.NoError(t, store.Reset()) // make sure the session is recreated sess, _ = store.Get(ctx) - utils.AssertEqual(t, true, sess.Fresh()) - utils.AssertEqual(t, nil, sess.Get("hello")) + require.True(t, sess.Fresh()) + require.Nil(t, sess.Get("hello")) } // go test -run Test_Session_Save @@ -243,7 +243,7 @@ func Test_Session_Save(t *testing.T) { // save session err := sess.Save() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) }) t.Run("save to header", func(t *testing.T) { @@ -263,9 +263,9 @@ func Test_Session_Save(t *testing.T) { // save session err := sess.Save() - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, store.getSessionID(ctx), string(ctx.Response().Header.Peek(store.sessionName))) - utils.AssertEqual(t, store.getSessionID(ctx), string(ctx.Request().Header.Peek(store.sessionName))) + require.NoError(t, err) + require.Equal(t, store.getSessionID(ctx), string(ctx.Response().Header.Peek(store.sessionName))) + require.Equal(t, store.getSessionID(ctx), string(ctx.Request().Header.Peek(store.sessionName))) }) } @@ -290,18 +290,18 @@ func Test_Session_Save_Expiration(t *testing.T) { // save session err := sess.Save() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // here you need to get the old session yet sess, _ = store.Get(ctx) - utils.AssertEqual(t, "john", sess.Get("name")) + require.Equal(t, "john", sess.Get("name")) // just to make sure the session has been expired time.Sleep(time.Second * 5) // here you should get a new session sess, _ = store.Get(ctx) - utils.AssertEqual(t, nil, sess.Get("name")) + require.Nil(t, sess.Get("name")) }) } @@ -321,9 +321,9 @@ func Test_Session_Reset(t *testing.T) { sess, _ := store.Get(ctx) sess.Set("name", "fenny") - sess.Destroy() + require.NoError(t, sess.Destroy()) name := sess.Get("name") - utils.AssertEqual(t, nil, name) + require.Nil(t, name) }) t.Run("reset from header", func(t *testing.T) { @@ -341,13 +341,13 @@ func Test_Session_Reset(t *testing.T) { // set value & save sess.Set("name", "fenny") - _ = sess.Save() + require.NoError(t, sess.Save()) sess, _ = store.Get(ctx) err := sess.Destroy() - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "", string(ctx.Response().Header.Peek(store.sessionName))) - utils.AssertEqual(t, "", string(ctx.Request().Header.Peek(store.sessionName))) + require.NoError(t, err) + require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) + require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) }) } @@ -356,11 +356,11 @@ func Test_Session_Custom_Config(t *testing.T) { t.Parallel() store := New(Config{Expiration: time.Hour, KeyGenerator: func() string { return "very random" }}) - utils.AssertEqual(t, time.Hour, store.Expiration) - utils.AssertEqual(t, "very random", store.KeyGenerator()) + require.Equal(t, time.Hour, store.Expiration) + require.Equal(t, "very random", store.KeyGenerator()) store = New(Config{Expiration: 0}) - utils.AssertEqual(t, ConfigDefault.Expiration, store.Expiration) + require.Equal(t, ConfigDefault.Expiration, store.Expiration) } // go test -run Test_Session_Cookie @@ -375,10 +375,10 @@ func Test_Session_Cookie(t *testing.T) { // get session sess, _ := store.Get(ctx) - sess.Save() + require.NoError(t, sess.Save()) // cookie should be set on Save ( even if empty data ) - utils.AssertEqual(t, 84, len(ctx.Response().Header.PeekCookie(store.sessionName))) + require.Equal(t, 84, len(ctx.Response().Header.PeekCookie(store.sessionName))) } // go test -run Test_Session_Cookie_In_Response @@ -393,15 +393,15 @@ func Test_Session_Cookie_In_Response(t *testing.T) { // get session sess, _ := store.Get(ctx) sess.Set("id", "1") - utils.AssertEqual(t, true, sess.Fresh()) - sess.Save() + require.True(t, sess.Fresh()) + require.NoError(t, sess.Save()) sess, _ = store.Get(ctx) sess.Set("name", "john") - utils.AssertEqual(t, true, sess.Fresh()) + require.True(t, sess.Fresh()) - utils.AssertEqual(t, "1", sess.Get("id")) - utils.AssertEqual(t, "john", sess.Get("name")) + require.Equal(t, "1", sess.Get("id")) + require.Equal(t, "john", sess.Get("name")) } // go test -run Test_Session_Deletes_Single_Key @@ -414,21 +414,21 @@ func Test_Session_Deletes_Single_Key(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) sess, err := store.Get(ctx) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) sess.Set("id", "1") - utils.AssertEqual(t, nil, sess.Save()) + require.Nil(t, sess.Save()) sess, err = store.Get(ctx) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) sess.Delete("id") - utils.AssertEqual(t, nil, sess.Save()) + require.Nil(t, sess.Save()) sess, err = store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, false, sess.Fresh()) - utils.AssertEqual(t, nil, sess.Get("id")) + require.NoError(t, err) + require.False(t, sess.Fresh()) + require.Nil(t, sess.Get("id")) } // go test -run Test_Session_Regenerate @@ -446,29 +446,29 @@ func Test_Session_Regenerate(t *testing.T) { // now the session is in the storage freshSession, err := store.Get(ctx) - utils.AssertEqual(t, nil, err) + require.NoError(t, err) originalSessionUUIDString = freshSession.ID() err = freshSession.Save() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) // set cookie ctx.Request().Header.SetCookie(store.sessionName, originalSessionUUIDString) // as the session is in the storage, session.fresh should be false acquiredSession, err := store.Get(ctx) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, false, acquiredSession.Fresh()) + require.NoError(t, err) + require.False(t, acquiredSession.Fresh()) err = acquiredSession.Regenerate() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) if acquiredSession.ID() == originalSessionUUIDString { t.Fatal("regenerate should generate another different id") } // acquiredSession.fresh should be true after regenerating - utils.AssertEqual(t, true, acquiredSession.Fresh()) + require.True(t, acquiredSession.Fresh()) }) } @@ -479,14 +479,17 @@ func Benchmark_Session(b *testing.B) { defer app.ReleaseCtx(c) c.Request().Header.SetCookie(store.sessionName, "12356789") + var err error b.Run("default", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { sess, _ := store.Get(c) sess.Set("john", "doe") - _ = sess.Save() + err = sess.Save() } + + require.NoError(b, err) }) b.Run("storage", func(b *testing.B) { @@ -498,7 +501,9 @@ func Benchmark_Session(b *testing.B) { for n := 0; n < b.N; n++ { sess, _ := store.Get(c) sess.Set("john", "doe") - _ = sess.Save() + err = sess.Save() } + + require.NoError(b, err) }) } diff --git a/middleware/session/store.go b/middleware/session/store.go index c85835e657..c3694a6ec5 100644 --- a/middleware/session/store.go +++ b/middleware/session/store.go @@ -6,7 +6,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/internal/storage/memory" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) @@ -66,7 +66,7 @@ func (s *Store) Get(c fiber.Ctx) (*Session, error) { // Fetch existing data if loadData { raw, err := s.Storage.Get(id) - // Unmashal if we found data + // Unmarshal if we found data if raw != nil && err == nil { mux.Lock() defer mux.Unlock() diff --git a/middleware/session/store_test.go b/middleware/session/store_test.go index c72d0891b8..b579e59379 100644 --- a/middleware/session/store_test.go +++ b/middleware/session/store_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -25,7 +25,7 @@ func TestStore_getSessionID(t *testing.T) { // set cookie ctx.Request().Header.SetCookie(store.sessionName, expectedID) - utils.AssertEqual(t, expectedID, store.getSessionID(ctx)) + require.Equal(t, expectedID, store.getSessionID(ctx)) }) t.Run("from header", func(t *testing.T) { @@ -39,7 +39,7 @@ func TestStore_getSessionID(t *testing.T) { // set header ctx.Request().Header.Set(store.sessionName, expectedID) - utils.AssertEqual(t, expectedID, store.getSessionID(ctx)) + require.Equal(t, expectedID, store.getSessionID(ctx)) }) t.Run("from url query", func(t *testing.T) { @@ -53,7 +53,7 @@ func TestStore_getSessionID(t *testing.T) { // set url parameter ctx.Request().SetRequestURI(fmt.Sprintf("/path?%s=%s", store.sessionName, expectedID)) - utils.AssertEqual(t, expectedID, store.getSessionID(ctx)) + require.Equal(t, expectedID, store.getSessionID(ctx)) }) } @@ -73,7 +73,7 @@ func TestStore_Get(t *testing.T) { ctx.Request().Header.SetCookie(store.sessionName, unexpectedID) acquiredSession, err := store.Get(ctx) - utils.AssertEqual(t, err, nil) + require.NoError(t, err) if acquiredSession.ID() != unexpectedID { t.Fatal("server should not accept the unexpectedID which is not in the store") diff --git a/middleware/skip/README.md b/middleware/skip/README.md index a79830c6b4..7806211459 100644 --- a/middleware/skip/README.md +++ b/middleware/skip/README.md @@ -15,8 +15,8 @@ func New(handler fiber.Handler, exclude func(c fiber.Ctx) bool) fiber.Handler Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/skip" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/skip" ) ``` diff --git a/middleware/skip/skip_test.go b/middleware/skip/skip_test.go index b8598bbffa..4373e18e37 100644 --- a/middleware/skip/skip_test.go +++ b/middleware/skip/skip_test.go @@ -6,7 +6,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/skip" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) // go test -run Test_Skip @@ -17,8 +17,8 @@ func Test_Skip(t *testing.T) { app.Get("/", helloWorldHandler) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) } // go test -run Test_SkipFalse @@ -29,8 +29,8 @@ func Test_SkipFalse(t *testing.T) { app.Get("/", helloWorldHandler) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusTeapot, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } // go test -run Test_SkipNilFunc @@ -41,8 +41,8 @@ func Test_SkipNilFunc(t *testing.T) { app.Get("/", helloWorldHandler) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, fiber.StatusTeapot, resp.StatusCode) + require.NoError(t, err) + require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } func helloWorldHandler(c fiber.Ctx) error { diff --git a/middleware/timeout/README.md b/middleware/timeout/README.md index 2af7bbab9f..4004927bc5 100644 --- a/middleware/timeout/README.md +++ b/middleware/timeout/README.md @@ -1,5 +1,9 @@ # Timeout -Timeout middleware for [Fiber](https://github.com/gofiber/fiber) wraps a `fiber.Handler` with a timeout. If the handler takes longer than the given duration to return, the timeout error is set and forwarded to the centralized [ErrorHandler](https://docs.gofiber.io/error-handling). +Timeout middleware for Fiber. As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` and pass it in `UserContext`. + +If the context passed executions (eg. DB ops, Http calls) takes longer than the given duration to return, the timeout error is set and forwarded to the centralized `ErrorHandler`. + +It has no race conditions, ready to use on production. ### Table of Contents - [Signatures](#signatures) @@ -8,27 +12,87 @@ Timeout middleware for [Fiber](https://github.com/gofiber/fiber) wraps a `fiber. ### Signatures ```go -func New(h fiber.Handler, t time.Duration) fiber.Handler +func New(handler fiber.Handler, timeout time.Duration, timeoutErrors ...error) fiber.Handler ``` ### Examples Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/timeout" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/timeout" ) ``` -After you initiate your Fiber app, you can use the following possibilities: +Sample timeout middleware usage ```go -handler := func(c fiber.Ctx) error { - err := ctx.SendString("Hello, World 👋!") - if err != nil { - return err +func main() { + app := fiber.New() + h := func(c fiber.Ctx) error { + sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + if err := sleepWithContext(c.UserContext(), sleepTime); err != nil { + return fmt.Errorf("%w: execution error", err) + } + return nil + } + + app.Get("/foo/:sleepTime", timeout.New(h, 2*time.Second)) + _ = app.Listen(":3000") +} + +func sleepWithContext(ctx context.Context, d time.Duration) error { + timer := time.NewTimer(d) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + return context.DeadlineExceeded + case <-timer.C: } return nil } +``` + +Test http 200 with curl: +```bash +curl --location -I --request GET 'http://localhost:3000/foo/1000' +``` + +Test http 408 with curl: +```bash +curl --location -I --request GET 'http://localhost:3000/foo/3000' +``` -app.Get("/foo", timeout.New(handler, 5 * time.Second)) + +When using with custom error: +```go +var ErrFooTimeOut = errors.New("foo context canceled") + +func main() { + app := fiber.New() + h := func(c *fiber.Ctx) error { + sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + if err := sleepWithContextWithCustomError(c.UserContext(), sleepTime); err != nil { + return fmt.Errorf("%w: execution error", err) + } + return nil + } + + app.Get("/foo/:sleepTime", timeout.New(h, 2*time.Second, ErrFooTimeOut)) + _ = app.Listen(":3000") +} + +func sleepWithContextWithCustomError(ctx context.Context, d time.Duration) error { + timer := time.NewTimer(d) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + return ErrFooTimeOut + case <-timer.C: + } + return nil +} ``` diff --git a/middleware/timeout/timeout.go b/middleware/timeout/timeout.go index a66b77e9ba..5a9711ce22 100644 --- a/middleware/timeout/timeout.go +++ b/middleware/timeout/timeout.go @@ -1,43 +1,30 @@ package timeout import ( - "fmt" - "sync" + "context" + "errors" "time" "github.com/gofiber/fiber/v3" ) -var once sync.Once - -// New wraps a handler and aborts the process of the handler if the timeout is reached -func New(handler fiber.Handler, timeout time.Duration) fiber.Handler { - once.Do(func() { - fmt.Println("[Warning] timeout contains data race issues, not ready for production!") - }) - - if timeout <= 0 { - return handler - } - - // logic is from fasthttp.TimeoutWithCodeHandler https://github.com/valyala/fasthttp/blob/master/server.go#L418 +// New implementation of timeout middleware. Set custom errors(context.DeadlineExceeded vs) for get fiber.ErrRequestTimeout response. +func New(h fiber.Handler, t time.Duration, tErrs ...error) fiber.Handler { return func(ctx fiber.Ctx) error { - ch := make(chan struct{}, 1) - - go func() { - defer func() { - _ = recover() - }() - _ = handler(ctx) - ch <- struct{}{} - }() - - select { - case <-ch: - case <-time.After(timeout): - return fiber.ErrRequestTimeout + timeoutContext, cancel := context.WithTimeout(ctx.UserContext(), t) + defer cancel() + ctx.SetUserContext(timeoutContext) + if err := h(ctx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return fiber.ErrRequestTimeout + } + for i := range tErrs { + if errors.Is(err, tErrs[i]) { + return fiber.ErrRequestTimeout + } + } + return err } - return nil } } diff --git a/middleware/timeout/timeout_test.go b/middleware/timeout/timeout_test.go index a6bed3b83d..4d5d4aa1aa 100644 --- a/middleware/timeout/timeout_test.go +++ b/middleware/timeout/timeout_test.go @@ -1,55 +1,84 @@ package timeout -// // go test -run Test_Middleware_Timeout -// func Test_Middleware_Timeout(t *testing.T) { -// app := fiber.New(fiber.Config{DisableStartupMessage: true}) +import ( + "context" + "errors" + "fmt" + "net/http/httptest" + "testing" + "time" -// h := New(func(c fiber.Ctx) error { -// sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") -// time.Sleep(sleepTime) -// return c.SendString("After " + c.Params("sleepTime") + "ms sleeping") -// }, 5*time.Millisecond) -// app.Get("/test/:sleepTime", h) + "github.com/gofiber/fiber/v3" + "github.com/stretchr/testify/require" +) -// testTimeout := func(timeoutStr string) { -// resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) -// utils.AssertEqual(t, nil, err, "app.Test(req)") -// utils.AssertEqual(t, fiber.StatusRequestTimeout, resp.StatusCode, "Status code") +// go test -run Test_Timeout +func Test_Timeout(t *testing.T) { + // fiber instance + app := fiber.New() + h := New(func(c fiber.Ctx) error { + sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + if err := sleepWithContext(c.UserContext(), sleepTime, context.DeadlineExceeded); err != nil { + return fmt.Errorf("%w: l2 wrap", fmt.Errorf("%w: l1 wrap ", err)) + } + return nil + }, 100*time.Millisecond) + app.Get("/test/:sleepTime", h) + testTimeout := func(timeoutStr string) { + resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, fiber.StatusRequestTimeout, resp.StatusCode, "Status code") + } + testSucces := func(timeoutStr string) { + resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") + } + testTimeout("300") + testTimeout("500") + testSucces("50") + testSucces("30") +} -// body, err := io.ReadAll(resp.Body) -// utils.AssertEqual(t, nil, err) -// utils.AssertEqual(t, "Request Timeout", string(body)) -// } -// testSucces := func(timeoutStr string) { -// resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) -// utils.AssertEqual(t, nil, err, "app.Test(req)") -// utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code") +var ErrFooTimeOut = errors.New("foo context canceled") -// body, err := io.ReadAll(resp.Body) -// utils.AssertEqual(t, nil, err) -// utils.AssertEqual(t, "After "+timeoutStr+"ms sleeping", string(body)) -// } +// go test -run Test_TimeoutWithCustomError +func Test_TimeoutWithCustomError(t *testing.T) { + // fiber instance + app := fiber.New() + h := New(func(c fiber.Ctx) error { + sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + if err := sleepWithContext(c.UserContext(), sleepTime, ErrFooTimeOut); err != nil { + return fmt.Errorf("%w: execution error", err) + } + return nil + }, 100*time.Millisecond, ErrFooTimeOut) + app.Get("/test/:sleepTime", h) + testTimeout := func(timeoutStr string) { + resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, fiber.StatusRequestTimeout, resp.StatusCode, "Status code") + } + testSucces := func(timeoutStr string) { + resp, err := app.Test(httptest.NewRequest("GET", "/test/"+timeoutStr, nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") + } + testTimeout("300") + testTimeout("500") + testSucces("50") + testSucces("30") +} -// testTimeout("15") -// testSucces("2") -// testTimeout("30") -// testSucces("3") -// } - -// // go test -run -v Test_Timeout_Panic -// func Test_Timeout_Panic(t *testing.T) { -// app := fiber.New(fiber.Config{DisableStartupMessage: true}) - -// app.Get("/panic", recover.New(), New(func(c fiber.Ctx) error { -// c.Set("dummy", "this should not be here") -// panic("panic in timeout handler") -// }, 5*time.Millisecond)) - -// resp, err := app.Test(httptest.NewRequest("GET", "/panic", nil)) -// utils.AssertEqual(t, nil, err, "app.Test(req)") -// utils.AssertEqual(t, fiber.StatusRequestTimeout, resp.StatusCode, "Status code") - -// body, err := io.ReadAll(resp.Body) -// utils.AssertEqual(t, nil, err) -// utils.AssertEqual(t, "Request Timeout", string(body)) -// } +func sleepWithContext(ctx context.Context, d time.Duration, te error) error { + timer := time.NewTimer(d) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + return te + case <-timer.C: + } + return nil +} diff --git a/mount.go b/mount.go new file mode 100644 index 0000000000..056ccbc791 --- /dev/null +++ b/mount.go @@ -0,0 +1,146 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "sort" + "strings" + "sync" + "sync/atomic" +) + +// Put fields related to mounting. +type mountFields struct { + // Mounted and main apps + appList map[string]*App + // Ordered keys of apps (sorted by key length for Render) + appListKeys []string + // check added routes of sub-apps + subAppsRoutesAdded sync.Once + // Prefix of app if it was mounted + mountPath string +} + +// Create empty mountFields instance +func newMountFields(app *App) *mountFields { + return &mountFields{ + appList: map[string]*App{"": app}, + appListKeys: make([]string, 0), + } +} + +// Mount attaches another app instance as a sub-router along a routing path. +// It's very useful to split up a large API as many independent routers and +// compose them as a single service using Mount. The fiber's error handler and +// any of the fiber's sub apps are added to the application's error handlers +// to be invoked on errors that happen within the prefix route. +func (app *App) mount(prefix string, fiber *App) Router { + prefix = strings.TrimRight(prefix, "/") + if prefix == "" { + prefix = "/" + } + + // Support for configs of mounted-apps and sub-mounted-apps + for mountedPrefixes, subApp := range fiber.mountFields.appList { + subApp.mountFields.mountPath = prefix + mountedPrefixes + app.mountFields.appList[prefix+mountedPrefixes] = subApp + } + + // Execute onMount hooks + if err := fiber.hooks.executeOnMountHooks(app); err != nil { + panic(err) + } + + return app +} + +// Mount attaches another app instance as a sub-router along a routing path. +// It's very useful to split up a large API as many independent routers and +// compose them as a single service using Mount. +func (grp *Group) mount(prefix string, fiber *App) Router { + groupPath := getGroupPath(grp.Prefix, prefix) + groupPath = strings.TrimRight(groupPath, "/") + if groupPath == "" { + groupPath = "/" + } + + // Support for configs of mounted-apps and sub-mounted-apps + for mountedPrefixes, subApp := range fiber.mountFields.appList { + subApp.mountFields.mountPath = groupPath + mountedPrefixes + grp.app.mountFields.appList[groupPath+mountedPrefixes] = subApp + } + + // Execute onMount hooks + if err := fiber.hooks.executeOnMountHooks(grp.app); err != nil { + panic(err) + } + + return grp +} + +// The MountPath property contains one or more path patterns on which a sub-app was mounted. +func (app *App) MountPath() string { + return app.mountFields.mountPath +} + +// generateAppListKeys generates app list keys for Render, should work after appendSubAppLists +func (app *App) generateAppListKeys() { + for key := range app.mountFields.appList { + app.mountFields.appListKeys = append(app.mountFields.appListKeys, key) + } + + sort.Slice(app.mountFields.appListKeys, func(i, j int) bool { + return len(app.mountFields.appListKeys[i]) < len(app.mountFields.appListKeys[j]) + }) +} + +// appendSubAppLists supports nested for sub apps +func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { + for prefix, subApp := range appList { + // skip real app + if prefix == "" { + continue + } + + if len(parent) > 0 { + prefix = parent[0] + prefix + } + + if _, ok := app.mountFields.appList[prefix]; !ok { + app.mountFields.appList[prefix] = subApp + } + + // The first element of appList is always the app itself. If there are no other sub apps, we should skip appending nested apps. + if len(subApp.mountFields.appList) > 1 { + app.appendSubAppLists(subApp.mountFields.appList, prefix) + } + + } +} + +// addSubAppsRoutes adds routes of sub apps nestedly when to start the server +func (app *App) addSubAppsRoutes(appList map[string]*App, parent ...string) { + for prefix, subApp := range appList { + // skip real app + if prefix == "" { + continue + } + + if len(parent) > 0 { + prefix = parent[0] + prefix + } + + // add routes + stack := subApp.stack + for m := range stack { + for r := range stack[m] { + route := app.copyRoute(stack[m][r]) + app.addRoute(route.Method, app.addPrefixToRoute(prefix, route), true) + } + } + + atomic.AddUint32(&app.handlersCount, subApp.handlersCount) + } +} diff --git a/mount_test.go b/mount_test.go new file mode 100644 index 0000000000..a48f03768c --- /dev/null +++ b/mount_test.go @@ -0,0 +1,360 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "errors" + "io" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +// go test -run Test_App_Mount +func Test_App_Mount(t *testing.T) { + micro := New() + micro.Get("/doe", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) + + app := New() + app.Use("/john", micro) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/john/doe", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, uint32(1), app.handlersCount) +} + +// go test -run Test_App_Mount_Nested +func Test_App_Mount_Nested(t *testing.T) { + app := New() + one := New() + two := New() + three := New() + + two.Use("/three", three) + app.Use("/one", one) + one.Use("/two", two) + + one.Get("/doe", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) + + two.Get("/nested", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) + + three.Get("/test", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/one/doe", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/one/two/nested", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/one/two/three/test", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + require.Equal(t, uint32(3), app.handlersCount) +} + +// go test -run Test_App_MountPath +func Test_App_MountPath(t *testing.T) { + app := New() + one := New() + two := New() + three := New() + + two.Use("/three", three) + one.Use("/two", two) + app.Use("/one", one) + + require.Equal(t, "/one", one.MountPath()) + require.Equal(t, "/one/two", two.MountPath()) + require.Equal(t, "/one/two/three", three.MountPath()) + require.Equal(t, "", app.MountPath()) +} + +func Test_App_ErrorHandler_GroupMount(t *testing.T) { + micro := New(Config{ + ErrorHandler: func(c Ctx, err error) error { + require.Equal(t, "0: GET error", err.Error()) + return c.Status(500).SendString("1: custom error") + }, + }) + micro.Get("/doe", func(c Ctx) error { + return errors.New("0: GET error") + }) + + app := New() + v1 := app.Group("/v1") + v1.Use("/john", micro) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) + testErrorResponse(t, err, resp, "1: custom error") +} + +func Test_App_ErrorHandler_GroupMountRootLevel(t *testing.T) { + micro := New(Config{ + ErrorHandler: func(c Ctx, err error) error { + require.Equal(t, "0: GET error", err.Error()) + return c.Status(500).SendString("1: custom error") + }, + }) + micro.Get("/john/doe", func(c Ctx) error { + return errors.New("0: GET error") + }) + + app := New() + v1 := app.Group("/v1") + v1.Use("/", micro) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) + testErrorResponse(t, err, resp, "1: custom error") +} + +// go test -run Test_App_Group_Mount +func Test_App_Group_Mount(t *testing.T) { + micro := New() + micro.Get("/doe", func(c Ctx) error { + return c.SendStatus(StatusOK) + }) + + app := New() + v1 := app.Group("/v1") + v1.Use("/john", micro) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, uint32(1), app.handlersCount) +} + +func Test_App_UseMountedErrorHandler(t *testing.T) { + app := New() + + fiber := New(Config{ + ErrorHandler: func(c Ctx, err error) error { + return c.Status(500).SendString("hi, i'm a custom error") + }, + }) + fiber.Get("/", func(c Ctx) error { + return errors.New("something happened") + }) + + app.Use("/api", fiber) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil)) + testErrorResponse(t, err, resp, "hi, i'm a custom error") +} + +func Test_App_UseMountedErrorHandlerRootLevel(t *testing.T) { + app := New() + + fiber := New(Config{ + ErrorHandler: func(c Ctx, err error) error { + return c.Status(500).SendString("hi, i'm a custom error") + }, + }) + fiber.Get("/api", func(c Ctx) error { + return errors.New("something happened") + }) + + app.Use("/", fiber) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil)) + testErrorResponse(t, err, resp, "hi, i'm a custom error") +} + +func Test_App_UseMountedErrorHandlerForBestPrefixMatch(t *testing.T) { + app := New() + + tsf := func(c Ctx, err error) error { + return c.Status(200).SendString("hi, i'm a custom sub sub fiber error") + } + tripleSubFiber := New(Config{ + ErrorHandler: tsf, + }) + tripleSubFiber.Get("/", func(c Ctx) error { + return errors.New("something happened") + }) + + sf := func(c Ctx, err error) error { + return c.Status(200).SendString("hi, i'm a custom sub fiber error") + } + subfiber := New(Config{ + ErrorHandler: sf, + }) + subfiber.Get("/", func(c Ctx) error { + return errors.New("something happened") + }) + subfiber.Use("/third", tripleSubFiber) + + f := func(c Ctx, err error) error { + return c.Status(200).SendString("hi, i'm a custom error") + } + fiber := New(Config{ + ErrorHandler: f, + }) + fiber.Get("/", func(c Ctx) error { + return errors.New("something happened") + }) + fiber.Use("/sub", subfiber) + + app.Use("/api", fiber) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub", nil)) + require.Equal(t, nil, err, "/api/sub req") + require.Equal(t, 200, resp.StatusCode, "Status code") + + b, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err, "iotuil.ReadAll()") + require.Equal(t, "hi, i'm a custom sub fiber error", string(b), "Response body") + + resp2, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub/third", nil)) + require.Equal(t, nil, err, "/api/sub/third req") + require.Equal(t, 200, resp.StatusCode, "Status code") + + b, err = io.ReadAll(resp2.Body) + require.Equal(t, nil, err, "iotuil.ReadAll()") + require.Equal(t, "hi, i'm a custom sub sub fiber error", string(b), "Third fiber Response body") +} + +// go test -run Test_Ctx_Render_Mount +func Test_Ctx_Render_Mount(t *testing.T) { + t.Parallel() + + engine := &testTemplateEngine{} + engine.Load() + + sub := New(Config{ + Views: engine, + }) + + sub.Get("/:name", func(c Ctx) error { + return c.Render("hello_world.tmpl", Map{ + "Name": c.Params("name"), + }) + }) + + app := New() + app.Use("/hello", sub) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/a", nil)) + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, nil, err, "app.Test(req)") + + body, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "

    Hello a!

    ", string(body)) +} + +// go test -run Test_Ctx_Render_Mount_ParentOrSubHasViews +func Test_Ctx_Render_Mount_ParentOrSubHasViews(t *testing.T) { + t.Parallel() + + engine := &testTemplateEngine{} + err := engine.Load() + require.Equal(t, nil, err) + + engine2 := &testTemplateEngine{path: "testdata2"} + err = engine2.Load() + require.Equal(t, nil, err) + + engine3 := &testTemplateEngine{path: "testdata3"} + err = engine3.Load() + require.Equal(t, nil, err) + + sub := New(Config{ + Views: engine3, + }) + + sub2 := New(Config{ + Views: engine2, + }) + + app := New(Config{ + Views: engine, + }) + + app.Get("/test", func(c Ctx) error { + return c.Render("index.tmpl", Map{ + "Title": "Hello, World!", + }) + }) + + sub.Get("/world/:name", func(c Ctx) error { + return c.Render("hello_world.tmpl", Map{ + "Name": c.Params("name"), + }) + }) + + sub2.Get("/moment", func(c Ctx) error { + return c.Render("bruh.tmpl", Map{}) + }) + + sub.Use("/bruh", sub2) + app.Use("/hello", sub) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/world/a", nil)) + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, nil, err, "app.Test(req)") + + body, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "

    Hello a!

    ", string(body)) + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil)) + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, nil, err, "app.Test(req)") + + body, err = io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "

    Hello, World!

    ", string(body)) + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/hello/bruh/moment", nil)) + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + require.Equal(t, nil, err, "app.Test(req)") + + body, err = io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "

    I'm Bruh

    ", string(body)) + +} + +func Test_Ctx_Render_MountGroup(t *testing.T) { + t.Parallel() + + engine := &testTemplateEngine{} + engine.Load() + + micro := New(Config{ + Views: engine, + }) + + micro.Get("/doe", func(c Ctx) error { + return c.Render("hello_world.tmpl", Map{ + "Name": "doe", + }) + }) + + app := New() + v1 := app.Group("/v1") + v1.Use("/john", micro) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil)) + require.Equal(t, nil, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + + body, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "

    Hello doe!

    ", string(body)) +} diff --git a/path.go b/path.go index 7840481416..ab96b36a5a 100644 --- a/path.go +++ b/path.go @@ -7,10 +7,13 @@ package fiber import ( + "regexp" "strconv" "strings" + "time" + "unicode" - "github.com/gofiber/fiber/v3/utils" + "github.com/google/uuid" ) // routeParser holds the path segments and param names @@ -33,20 +36,55 @@ type routeSegment struct { IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus IsOptional bool // indicates whether the parameter is optional or not // common information - IsLast bool // shows if the segment is the last one for the route - HasOptionalSlash bool // segment has the possibility of an optional slash - Length int // length of the parameter for segment, when its 0 then the length is undetermined + IsLast bool // shows if the segment is the last one for the route + HasOptionalSlash bool // segment has the possibility of an optional slash + Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default + Length int // length of the parameter for segment, when its 0 then the length is undetermined // future TODO: add support for optional groups "/abc(/def)?" } // different special routing signs const ( - wildcardParam byte = '*' // indicates a optional greedy parameter - plusParam byte = '+' // indicates a required greedy parameter - optionalParam byte = '?' // concludes a parameter by name and makes it optional - paramStarterChar byte = ':' // start character for a parameter with name - slashDelimiter byte = '/' // separator for the route, unlike the other delimiters this character at the end can be optional - escapeChar byte = '\\' // escape character + wildcardParam byte = '*' // indicates a optional greedy parameter + plusParam byte = '+' // indicates a required greedy parameter + optionalParam byte = '?' // concludes a parameter by name and makes it optional + paramStarterChar byte = ':' // start character for a parameter with name + slashDelimiter byte = '/' // separator for the route, unlike the other delimiters this character at the end can be optional + slashDelimiterStr = "/" // separator for the route, unlike the other delimiters this character at the end can be optional + escapeChar byte = '\\' // escape character + paramConstraintStart byte = '<' // start of type constraint for a parameter + paramConstraintEnd byte = '>' // end of type constraint for a parameter + paramConstraintSeparator byte = ';' // separator of type constraints for a parameter + paramConstraintDataStart byte = '(' // start of data of type constraint for a parameter + paramConstraintDataEnd byte = ')' // end of data of type constraint for a parameter + paramConstraintDataSeparator byte = ',' // separator of datas of type constraint for a parameter +) + +// TypeConstraint parameter constraint types +type TypeConstraint int16 + +type Constraint struct { + ID TypeConstraint + RegexCompiler *regexp.Regexp + Data []string +} + +const ( + noConstraint TypeConstraint = iota + 1 + intConstraint + boolConstraint + floatConstraint + alphaConstraint + datetimeConstraint + guidConstraint + minLenConstraint + maxLenConstraint + lenConstraint + betweenLenConstraint + minConstraint + maxConstraint + rangeConstraint + regexConstraint ) // list of possible parameter and segment delimiter @@ -58,9 +96,21 @@ var ( // list of chars for the parameter recognising parameterStartChars = []byte{wildcardParam, plusParam, paramStarterChar} // list of chars of delimiters and the starting parameter name char - parameterDelimiterChars = append([]byte{paramStarterChar}, routeDelimiter...) + parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...) // list of chars to find the end of a parameter parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...) + // list of parameter constraint start + parameterConstraintStartChars = []byte{paramConstraintStart} + // list of parameter constraint end + parameterConstraintEndChars = []byte{paramConstraintEnd} + // list of parameter separator + parameterConstraintSeparatorChars = []byte{paramConstraintSeparator} + // list of parameter constraint data start + parameterConstraintDataStartChars = []byte{paramConstraintDataStart} + // list of parameter constraint data end + parameterConstraintDataEndChars = []byte{paramConstraintDataEnd} + // list of parameter constraint data separator + parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator} ) // parseRoute analyzes the route and divides it into segments for constant areas and parameters, @@ -109,7 +159,7 @@ func addParameterMetaInfo(segs []*routeSegment) []*routeSegment { } else { comparePart = segs[i].Const if len(comparePart) > 1 { - comparePart = utils.TrimRight(comparePart, slashDelimiter) + comparePart = strings.TrimRight(comparePart, slashDelimiterStr) } } } @@ -177,8 +227,16 @@ func (routeParser *routeParser) analyseConstantPart(pattern string, nextParamPos func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *routeSegment) { isWildCard := pattern[0] == wildcardParam isPlusParam := pattern[0] == plusParam - parameterEndPosition := findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars) + var parameterEndPosition int + if strings.ContainsRune(pattern, rune(paramConstraintStart)) && strings.ContainsRune(pattern, rune(paramConstraintEnd)) { + parameterEndPosition = findNextCharsetPositionConstraint(pattern[1:], parameterEndChars) + } else { + parameterEndPosition = findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars) + } + + parameterConstraintStart := -1 + parameterConstraintEnd := -1 // handle wildcard end if isWildCard || isPlusParam { parameterEndPosition = 0 @@ -187,10 +245,63 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r } else if !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars) { parameterEndPosition++ } + + // find constraint part if exists in the parameter part and remove it + if parameterEndPosition > 0 { + parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars) + parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars) + } + // cut params part processedPart := pattern[0 : parameterEndPosition+1] - paramName := RemoveEscapeChar(GetTrimmedParam(processedPart)) + + // Check has constraint + var constraints []*Constraint + + if hasConstraint := parameterConstraintStart != -1 && parameterConstraintEnd != -1; hasConstraint { + constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] + userConstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars)) + constraints = make([]*Constraint, 0, len(userConstraints)) + + for _, c := range userConstraints { + start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars) + end := findLastCharsetPosition(c, parameterConstraintDataEndChars) + + // Assign constraint + if start != -1 && end != -1 { + constraint := &Constraint{ + ID: getParamConstraintType(c[:start]), + Data: splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)), + } + + // remove escapes from data + if constraint.ID != regexConstraint { + if len(constraint.Data) == 1 { + constraint.Data[0] = RemoveEscapeChar(constraint.Data[0]) + } else if len(constraint.Data) == 2 { + constraint.Data[0] = RemoveEscapeChar(constraint.Data[0]) + constraint.Data[1] = RemoveEscapeChar(constraint.Data[1]) + } + } + + // Precompile regex if has regex constraint + if constraint.ID == regexConstraint { + constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0]) + } + + constraints = append(constraints, constraint) + } else { + constraints = append(constraints, &Constraint{ + ID: getParamConstraintType(c), + Data: []string{}, + }) + } + } + + paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart])) + } + // add access iterator to wildcard and plus if isWildCard { routeParser.wildCardCount++ @@ -200,12 +311,18 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r paramName += strconv.Itoa(routeParser.plusCount) } - return processedPart, &routeSegment{ + segment := &routeSegment{ ParamName: paramName, IsParam: true, IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam, IsGreedy: isWildCard || isPlusParam, } + + if len(constraints) > 0 { + segment.Constraints = constraints + } + + return processedPart, segment } // isInCharset check is the given character in the charset list @@ -230,6 +347,38 @@ func findNextCharsetPosition(search string, charset []byte) int { return nextPosition } +// findNextCharsetPosition search the last char position from the charset +func findLastCharsetPosition(search string, charset []byte) int { + lastPosition := -1 + for _, char := range charset { + if pos := strings.LastIndexByte(search, char); pos != -1 && (pos < lastPosition || lastPosition == -1) { + lastPosition = pos + } + } + + return lastPosition +} + +// findNextCharsetPositionConstraint search the next char position from the charset +// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern +func findNextCharsetPositionConstraint(search string, charset []byte) int { + constraintStart := findNextNonEscapedCharsetPosition(search, parameterConstraintStartChars) + constraintEnd := findNextNonEscapedCharsetPosition(search, parameterConstraintEndChars) + nextPosition := -1 + + for _, char := range charset { + pos := strings.IndexByte(search, char) + + if pos != -1 && (pos < nextPosition || nextPosition == -1) { + if (pos > constraintStart && pos > constraintEnd) || (pos < constraintStart && pos < constraintEnd) { + nextPosition = pos + } + } + } + + return nextPosition +} + // findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters func findNextNonEscapedCharsetPosition(search string, charset []byte) int { pos := findNextCharsetPosition(search, charset) @@ -249,6 +398,21 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int { return pos } +// splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators +// This function also takes a care of escape char when splitting. +func splitNonEscaped(s, sep string) []string { + var result []string + i := findNextNonEscapedCharsetPosition(s, []byte(sep)) + + for i > -1 { + result = append(result, s[:i]) + s = s[i+len(sep):] + i = findNextNonEscapedCharsetPosition(s, []byte(sep)) + } + + return append(result, s) +} + // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { var i, paramsIterator, partLen int @@ -272,6 +436,14 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma } // take over the params positions params[paramsIterator] = path[:i] + + // check constraint + for _, c := range segment.Constraints { + if matched := c.CheckConstraint(params[paramsIterator]); !matched { + return false + } + } + paramsIterator++ } @@ -370,3 +542,133 @@ func RemoveEscapeChar(word string) string { } return word } + +func getParamConstraintType(constraintPart string) TypeConstraint { + switch constraintPart { + case ConstraintInt: + return intConstraint + case ConstraintBool: + return boolConstraint + case ConstraintFloat: + return floatConstraint + case ConstraintAlpha: + return alphaConstraint + case ConstraintGuid: + return guidConstraint + case ConstraintMinLen, ConstraintMinLenLower: + return minLenConstraint + case ConstraintMaxLen, ConstraintMaxLenLower: + return maxLenConstraint + case ConstraintLen: + return lenConstraint + case ConstraintBetweenLen, ConstraintBetweenLenLower: + return betweenLenConstraint + case ConstraintMin: + return minConstraint + case ConstraintMax: + return maxConstraint + case ConstraintRange: + return rangeConstraint + case ConstraintDatetime: + return datetimeConstraint + case ConstraintRegex: + return regexConstraint + default: + return noConstraint + } + +} + +func (c *Constraint) CheckConstraint(param string) bool { + var err error + var num int + + // check data exists + needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint} + needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint} + + for _, data := range needOneData { + if c.ID == data && len(c.Data) == 0 { + return false + } + } + + for _, data := range needTwoData { + if c.ID == data && len(c.Data) < 2 { + return false + } + } + + // check constraints + switch c.ID { + case intConstraint: + _, err = strconv.Atoi(param) + case boolConstraint: + _, err = strconv.ParseBool(param) + case floatConstraint: + _, err = strconv.ParseFloat(param, 32) + case alphaConstraint: + for _, r := range param { + if !unicode.IsLetter(r) { + return false + } + } + case guidConstraint: + _, err = uuid.Parse(param) + case minLenConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) < data { + return false + } + case maxLenConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) > data { + return false + } + case lenConstraint: + data, _ := strconv.Atoi(c.Data[0]) + + if len(param) != data { + return false + } + case betweenLenConstraint: + data, _ := strconv.Atoi(c.Data[0]) + data2, _ := strconv.Atoi(c.Data[1]) + length := len(param) + if length < data || length > data2 { + return false + } + case minConstraint: + data, _ := strconv.Atoi(c.Data[0]) + num, err = strconv.Atoi(param) + + if num < data { + return false + } + case maxConstraint: + data, _ := strconv.Atoi(c.Data[0]) + num, err = strconv.Atoi(param) + + if num > data { + return false + } + case rangeConstraint: + data, _ := strconv.Atoi(c.Data[0]) + data2, _ := strconv.Atoi(c.Data[1]) + num, err = strconv.Atoi(param) + + if num < data || num > data2 { + return false + } + case datetimeConstraint: + _, err = time.Parse(c.Data[0], param) + case regexConstraint: + if match := c.RegexCompiler.MatchString(param); !match { + return false + } + } + + return err == nil +} diff --git a/path_test.go b/path_test.go index 13a11bf5ac..6ebf556278 100644 --- a/path_test.go +++ b/path_test.go @@ -8,7 +8,7 @@ import ( "fmt" "testing" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) // go test -race -run Test_Path_parseRoute @@ -16,7 +16,7 @@ func Test_Path_parseRoute(t *testing.T) { var rp routeParser rp = parseRoute("/shop/product/::filter/color::color/size::size") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/shop/product/:", Length: 15}, {IsParam: true, ParamName: "filter", ComparePart: "/color:", PartCount: 1}, @@ -29,7 +29,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/api/v1/:param/abc/*") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/api/v1/", Length: 8}, {IsParam: true, ParamName: "param", ComparePart: "/abc", PartCount: 1}, @@ -41,15 +41,26 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/v1/some/resource/name\\:customVerb") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/v1/some/resource/name:customVerb", Length: 33, IsLast: true}, }, params: nil, }, rp) + + rp = parseRoute("/v1/some/resource/:name\\:customVerb") + require.Equal(t, routeParser{ + segs: []*routeSegment{ + {Const: "/v1/some/resource/", Length: 18}, + {IsParam: true, ParamName: "name", ComparePart: ":customVerb", PartCount: 1}, + {Const: ":customVerb", Length: 11, IsLast: true}, + }, + params: []string{"name"}, + }, rp) + // heavy test with escaped charaters rp = parseRoute("/v1/some/resource/name\\\\:customVerb?\\?/:param/*") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/v1/some/resource/name:customVerb??/", Length: 36}, {IsParam: true, ParamName: "param", ComparePart: "/", PartCount: 1}, @@ -61,7 +72,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/api/*/:param/:param2") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/api/", Length: 5, HasOptionalSlash: true}, {IsParam: true, ParamName: "*1", IsGreedy: true, IsOptional: true, ComparePart: "/", PartCount: 2}, @@ -75,7 +86,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/test:optional?:optional2?") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/test", Length: 5}, {IsParam: true, ParamName: "optional", IsOptional: true, Length: 1}, @@ -85,7 +96,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/config/+.json") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/config/", Length: 8}, {IsParam: true, ParamName: "+1", IsGreedy: true, IsOptional: false, ComparePart: ".json", PartCount: 1}, @@ -96,7 +107,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/api/:day.:month?.:year?") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/api/", Length: 5}, {IsParam: true, ParamName: "day", IsOptional: false, ComparePart: ".", PartCount: 2}, @@ -109,7 +120,7 @@ func Test_Path_parseRoute(t *testing.T) { }, rp) rp = parseRoute("/*v1*/proxy") - utils.AssertEqual(t, routeParser{ + require.Equal(t, routeParser{ segs: []*routeSegment{ {Const: "/", Length: 1, HasOptionalSlash: true}, {IsParam: true, ParamName: "*1", IsGreedy: true, IsOptional: true, ComparePart: "v1", PartCount: 1}, @@ -120,6 +131,7 @@ func Test_Path_parseRoute(t *testing.T) { params: []string{"*1", "*2"}, wildCardCount: 2, }, rp) + } // go test -race -run Test_Path_matchParams @@ -136,9 +148,9 @@ func Test_Path_matchParams(t *testing.T) { parser := parseRoute(r) for _, c := range cases { match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck) - utils.AssertEqual(t, c.match, match, fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) + require.Equal(t, c.match, match, fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) if match && len(c.params) > 0 { - utils.AssertEqual(t, c.params[0:len(c.params)], ctxParams[0:len(c.params)], fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) + require.Equal(t, c.params[0:len(c.params)], ctxParams[0:len(c.params)], fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) } } } @@ -170,6 +182,10 @@ func Test_Path_matchParams(t *testing.T) { {url: "/v1/some/resource/name:customVerb", params: nil, match: true}, {url: "/v1/some/resource/name:test", params: nil, match: false}, }) + testCase("/v1/some/resource/:name\\:customVerb", []testparams{ + {url: "/v1/some/resource/test:customVerb", params: []string{"test"}, match: true}, + {url: "/v1/some/resource/test:test", params: nil, match: false}, + }) testCase("/v1/some/resource/name\\\\:customVerb?\\?/:param/*", []testparams{ {url: "/v1/some/resource/name:customVerb??/test/optionalWildCard/character", params: []string{"test", "optionalWildCard/character"}, match: true}, {url: "/v1/some/resource/name:customVerb??/test", params: []string{"test", ""}, match: true}, @@ -416,30 +432,196 @@ func Test_Path_matchParams(t *testing.T) { {url: "/api", params: nil, match: false}, {url: "/api/:test", params: nil, match: false}, }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/true", params: []string{"true"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/8728382.5", params: []string{"8728382.5"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/#!?", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", params: []string{"f0fa66cc-d22e-445b-866d-1d76e776371d"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/123", params: nil, match: false}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/ent", params: []string{"ent"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/123", params: nil, match: false}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/ent", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", params: nil, match: false}, + {url: "/api/v1/en", params: []string{"en"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", params: nil, match: false}, + {url: "/api/v1/en", params: []string{"en"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/1", params: nil, match: false}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/1", params: []string{"1"}, match: true}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + {url: "/api/v1/15", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/9", params: []string{"9"}, match: true}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + {url: "/api/v1/15", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/15", params: nil, match: false}, + {url: "/api/v1/peach", params: []string{"peach"}, match: true}, + {url: "/api/v1/p34ch", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/15", params: nil, match: false}, + {url: "/api/v1/2022-08-27", params: []string{"2022-08-27"}, match: true}, + {url: "/api/v1/2022/08-27", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/true", params: []string{"true"}, match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/1200", params: []string{"1200"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + testCase("/api/v1/:lang/videos/:page", []testparams{ + {url: "/api/v1/try/videos/200", params: nil, match: false}, + {url: "/api/v1/tr/videos/1800", params: nil, match: false}, + {url: "/api/v1/tr/videos/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/videos/10", params: nil, match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: nil, match: false}, + {url: "/api/v1/tr/1800", params: nil, match: false}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: []string{"try", "200"}, match: true}, + {url: "/api/v1/tr/1800", params: nil, match: false}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: nil, match: false}, + {url: "/api/v1/tr/1800", params: []string{"tr", "1800"}, match: true}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + testCase("/api/v1/:date/:regex", []testparams{ + {url: "/api/v1/2005-11-01/a", params: nil, match: false}, + {url: "/api/v1/2005-1101/paach", params: nil, match: false}, + {url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true}, + }) } func Test_Utils_GetTrimmedParam(t *testing.T) { t.Parallel() res := GetTrimmedParam("") - utils.AssertEqual(t, "", res) + require.Equal(t, "", res) res = GetTrimmedParam("*") - utils.AssertEqual(t, "*", res) + require.Equal(t, "*", res) res = GetTrimmedParam(":param") - utils.AssertEqual(t, "param", res) + require.Equal(t, "param", res) res = GetTrimmedParam(":param1?") - utils.AssertEqual(t, "param1", res) + require.Equal(t, "param1", res) res = GetTrimmedParam("noParam") - utils.AssertEqual(t, "noParam", res) + require.Equal(t, "noParam", res) } func Test_Utils_RemoveEscapeChar(t *testing.T) { t.Parallel() res := RemoveEscapeChar(":test\\:bla") - utils.AssertEqual(t, ":test:bla", res) + require.Equal(t, ":test:bla", res) res = RemoveEscapeChar("\\abc") - utils.AssertEqual(t, "abc", res) + require.Equal(t, "abc", res) res = RemoveEscapeChar("noEscapeChar") - utils.AssertEqual(t, "noEscapeChar", res) + require.Equal(t, "noEscapeChar", res) } // go test -race -run Test_Path_matchParams @@ -466,9 +648,9 @@ func Benchmark_Path_matchParams(t *testing.B) { matchRes = true } } - utils.AssertEqual(t, c.match, matchRes, fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) + require.Equal(t, c.match, matchRes, fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) if matchRes && len(c.params) > 0 { - utils.AssertEqual(t, c.params[0:len(c.params)-1], ctxParams[0:len(c.params)-1], fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) + require.Equal(t, c.params[0:len(c.params)-1], ctxParams[0:len(c.params)-1], fmt.Sprintf("route: '%s', url: '%s'", r, c.url)) } }) @@ -504,4 +686,170 @@ func Benchmark_Path_matchParams(t *testing.B) { {url: "/api/v2", params: nil, match: false}, {url: "/api/v1/", params: nil, match: false}, }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/true", params: []string{"true"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/8728382.5", params: []string{"8728382.5"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/#!?", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", params: []string{"f0fa66cc-d22e-445b-866d-1d76e776371d"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/123", params: nil, match: false}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/ent", params: []string{"ent"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/123", params: nil, match: false}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/ent", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", params: nil, match: false}, + {url: "/api/v1/en", params: []string{"en"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", params: nil, match: false}, + {url: "/api/v1/en", params: []string{"en"}, match: true}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/12345", params: []string{"12345"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/1", params: nil, match: false}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/1", params: []string{"1"}, match: true}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + {url: "/api/v1/15", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/9", params: []string{"9"}, match: true}, + {url: "/api/v1/5", params: []string{"5"}, match: true}, + {url: "/api/v1/15", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/15", params: nil, match: false}, + {url: "/api/v1/peach", params: []string{"peach"}, match: true}, + {url: "/api/v1/p34ch", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", params: nil, match: false}, + {url: "/api/v1/15", params: nil, match: false}, + {url: "/api/v1/2022-08-27", params: []string{"2022-08-27"}, match: true}, + {url: "/api/v1/2022/08-27", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/123", params: []string{"123"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: []string{"entity"}, match: true}, + {url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/true", params: []string{"true"}, match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/87283827683", params: nil, match: false}, + {url: "/api/v1/25", params: []string{"25"}, match: true}, + {url: "/api/v1/1200", params: []string{"1200"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + }) + benchCase("/api/v1/:lang/videos/:page", []testparams{ + {url: "/api/v1/try/videos/200", params: nil, match: false}, + {url: "/api/v1/tr/videos/1800", params: nil, match: false}, + {url: "/api/v1/tr/videos/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/videos/10", params: nil, match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: nil, match: false}, + {url: "/api/v1/tr/1800", params: nil, match: false}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: []string{"try", "200"}, match: true}, + {url: "/api/v1/tr/1800", params: nil, match: false}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", params: nil, match: false}, + {url: "/api/v1/tr/1800", params: []string{"tr", "1800"}, match: true}, + {url: "/api/v1/tr/100", params: []string{"tr", "100"}, match: true}, + {url: "/api/v1/e/10", params: nil, match: false}, + }) + benchCase("/api/v1/:date/:regex", []testparams{ + {url: "/api/v1/2005-11-01/a", params: nil, match: false}, + {url: "/api/v1/2005-1101/paach", params: nil, match: false}, + {url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true}, + }) } diff --git a/prefork.go b/prefork.go index 459e408930..71db86b8d7 100644 --- a/prefork.go +++ b/prefork.go @@ -20,6 +20,7 @@ const ( ) var testPreforkMaster = false +var testOnPrefork = false // IsChild determines if the current process is a child of Prefork func IsChild() bool { @@ -27,7 +28,7 @@ func IsChild() bool { } // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature -func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) { +func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) (err error) { // 👶 child process 👶 if IsChild() { // use 1 cpu core per child process @@ -35,8 +36,8 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) var ln net.Listener // Linux will use SO_REUSEPORT and Windows falls back to SO_REUSEADDR // Only tcp4 or tcp6 is supported when preforking, both are not supported - if ln, err = reuseport.Listen(network, addr); err != nil { - if !app.config.DisableStartupMessage { + if ln, err = reuseport.Listen(cfg.ListenerNetwork, addr); err != nil { + if !cfg.DisableStartupMessage { time.Sleep(100 * time.Millisecond) // avoid colliding with startup message } return fmt.Errorf("prefork: %v", err) @@ -52,6 +53,10 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) // prepare the server for the start app.startupProcess() + if cfg.ListenerAddrFunc != nil { + cfg.ListenerAddrFunc(ln.Addr()) + } + // listen for incoming connections return app.server.Serve(ln) } @@ -93,6 +98,7 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal), ) + if err = cmd.Start(); err != nil { return fmt.Errorf("failed to start a child prefork process, error: %v", err) } @@ -102,6 +108,15 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) childs[pid] = cmd pids = append(pids, strconv.Itoa(pid)) + // execute fork hook + if app.hooks != nil { + if testOnPrefork { + app.hooks.executeOnForkHooks(dummyPid) + } else { + app.hooks.executeOnForkHooks(pid) + } + } + // notify master if child crashes go func() { channel <- child{pid, cmd.Wait()} @@ -109,8 +124,13 @@ func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) } // Print startup message - if !app.config.DisableStartupMessage { - app.startupMessage(addr, tlsConfig != nil, ","+strings.Join(pids, ",")) + if !cfg.DisableStartupMessage { + app.startupMessage(addr, tlsConfig != nil, ","+strings.Join(pids, ","), cfg) + } + + // Print routes + if cfg.EnablePrintRoutes { + app.printRoutesMessage() } // return error if child crashes @@ -146,3 +166,5 @@ func dummyCmd() *exec.Cmd { } return exec.Command(dummyChildCmd, "version") } + +var dummyPid = 1 diff --git a/prefork_test.go b/prefork_test.go index f318ac37b9..a1a680c1bd 100644 --- a/prefork_test.go +++ b/prefork_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/stretchr/testify/require" ) func Test_App_Prefork_Child_Process(t *testing.T) { @@ -23,29 +23,29 @@ func Test_App_Prefork_Child_Process(t *testing.T) { app := New() - err := app.prefork(NetworkTCP4, "invalid", nil) - utils.AssertEqual(t, false, err == nil) + err := app.prefork("invalid", nil, listenConfigDefault()) + require.False(t, err == nil) go func() { time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.prefork(NetworkTCP6, "[::1]:", nil)) + require.Nil(t, app.prefork("[::1]:", nil, ListenConfig{ListenerNetwork: NetworkTCP6})) // Create tls certificate cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") if err != nil { - utils.AssertEqual(t, nil, err) + require.NoError(t, err) } config := &tls.Config{Certificates: []tls.Certificate{cer}} go func() { time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.prefork(NetworkTCP4, "127.0.0.1:", config)) + require.Nil(t, app.prefork("127.0.0.1:", config, listenConfigDefault())) } func Test_App_Prefork_Master_Process(t *testing.T) { @@ -56,15 +56,17 @@ func Test_App_Prefork_Master_Process(t *testing.T) { go func() { time.Sleep(1000 * time.Millisecond) - utils.AssertEqual(t, nil, app.Shutdown()) + require.Nil(t, app.Shutdown()) }() - utils.AssertEqual(t, nil, app.prefork(NetworkTCP4, ":3000", nil)) + require.Nil(t, app.prefork(":3000", nil, listenConfigDefault())) dummyChildCmd = "invalid" - err := app.prefork(NetworkTCP4, "127.0.0.1:", nil) - utils.AssertEqual(t, false, err == nil) + err := app.prefork("127.0.0.1:", nil, listenConfigDefault()) + require.False(t, err == nil) + + dummyChildCmd = "go" } func Test_App_Prefork_Child_Process_Never_Show_Startup_Message(t *testing.T) { @@ -75,27 +77,27 @@ func Test_App_Prefork_Child_Process_Never_Show_Startup_Message(t *testing.T) { defer func() { os.Stdout = rescueStdout }() r, w, err := os.Pipe() - utils.AssertEqual(t, nil, err) + require.NoError(t, err) os.Stdout = w - New().startupProcess().startupMessage(":3000", false, "") + New().startupProcess().startupMessage(":3000", false, "", listenConfigDefault()) - utils.AssertEqual(t, nil, w.Close()) + require.Nil(t, w.Close()) out, err := io.ReadAll(r) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, 0, len(out)) + require.NoError(t, err) + require.Equal(t, 0, len(out)) } func setupIsChild(t *testing.T) { t.Helper() - utils.AssertEqual(t, nil, os.Setenv(envPreforkChildKey, envPreforkChildVal)) + require.Nil(t, os.Setenv(envPreforkChildKey, envPreforkChildVal)) } func teardownIsChild(t *testing.T) { t.Helper() - utils.AssertEqual(t, nil, os.Setenv(envPreforkChildKey, "")) + require.Nil(t, os.Setenv(envPreforkChildKey, "")) } diff --git a/redirect.go b/redirect.go new file mode 100644 index 0000000000..e6ae673bfb --- /dev/null +++ b/redirect.go @@ -0,0 +1,295 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 📝 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "strings" + "sync" + + "github.com/gofiber/fiber/v3/binder" + "github.com/gofiber/utils" + "github.com/valyala/bytebufferpool" +) + +var ( + // Pool for redirection + redirectPool = sync.Pool{ + New: func() any { + return &Redirect{ + status: StatusFound, + oldInput: make(map[string]string, 0), + } + }, + } +) + +// Cookie name to send flash messages when to use redirection. +const ( + FlashCookieName = "fiber_flash" + OldInputDataPrefix = "old_input_data_" + CookieDataSeparator = "," + CookieDataAssigner = ":" +) + +// Redirect is a struct to use it with Ctx. +type Redirect struct { + c *DefaultCtx // Embed ctx + status int // Status code of redirection. Default: StatusFound + + messages []string // Flash messages + oldInput map[string]string // Old input data +} + +// A config to use with Redirect().Route() +// You can specify queries or route parameters. +// NOTE: We don't use net/url to parse parameters because of it has poor performance. You have to pass map. +type RedirectConfig struct { + Params Map // Route parameters + Queries map[string]string // Query map +} + +// AcquireRedirect return default Redirect reference from the redirect pool +func AcquireRedirect() *Redirect { + return redirectPool.Get().(*Redirect) +} + +// ReleaseRedirect returns c acquired via Redirect to redirect pool. +// +// It is forbidden accessing req and/or its' members after returning +// it to redirect pool. +func ReleaseRedirect(r *Redirect) { + r.release() + redirectPool.Put(r) +} + +func (r *Redirect) release() { + r.status = 302 + r.messages = r.messages[:0] + // reset map + for k := range r.oldInput { + delete(r.oldInput, k) + } + r.c = nil +} + +// Status sets the status code of redirection. +// If status is not specified, status defaults to 302 Found. +func (r *Redirect) Status(code int) *Redirect { + r.status = code + + return r +} + +// You can send flash messages by using With(). +// They will be sent as a cookie. +// You can get them by using: Redirect().Messages(), Redirect().Message() +// Note: You must use escape char before using ',' and ':' chars to avoid wrong parsing. +func (r *Redirect) With(key string, value string) *Redirect { + r.messages = append(r.messages, key+CookieDataAssigner+value) + + return r +} + +// You can send input data by using WithInput(). +// They will be sent as a cookie. +// This method can send form, multipart form, query data to redirected route. +// You can get them by using: Redirect().OldInputs(), Redirect().OldInput() +func (r *Redirect) WithInput() *Redirect { + // Get content-type + ctype := utils.ToLower(utils.UnsafeString(r.c.Context().Request.Header.ContentType())) + ctype = binder.FilterFlags(utils.ParseVendorSpecificContentType(ctype)) + + switch ctype { + case MIMEApplicationForm: + _ = r.c.Bind().Form(r.oldInput) + case MIMEMultipartForm: + _ = r.c.Bind().MultipartForm(r.oldInput) + default: + _ = r.c.Bind().Query(r.oldInput) + } + + return r +} + +// Get flash messages. +func (r *Redirect) Messages() map[string]string { + msgs := r.c.redirectionMessages + flashMessages := make(map[string]string, len(msgs)) + + for _, msg := range msgs { + k, v := parseMessage(msg) + + if !strings.HasPrefix(k, OldInputDataPrefix) { + flashMessages[k] = v + } + } + + return flashMessages +} + +// Get flash message by key. +func (r *Redirect) Message(key string) string { + msgs := r.c.redirectionMessages + + for _, msg := range msgs { + k, v := parseMessage(msg) + + if !strings.HasPrefix(k, OldInputDataPrefix) && k == key { + return v + } + } + return "" +} + +// Get old input data. +func (r *Redirect) OldInputs() map[string]string { + msgs := r.c.redirectionMessages + oldInputs := make(map[string]string, len(msgs)) + + for _, msg := range msgs { + k, v := parseMessage(msg) + + if strings.HasPrefix(k, OldInputDataPrefix) { + // remove "old_input_data_" part from key + oldInputs[k[len(OldInputDataPrefix):]] = v + } + } + return oldInputs +} + +// Get old input data by key. +func (r *Redirect) OldInput(key string) string { + msgs := r.c.redirectionMessages + + for _, msg := range msgs { + k, v := parseMessage(msg) + + if strings.HasPrefix(k, OldInputDataPrefix) && k[len(OldInputDataPrefix):] == key { + return v + } + } + return "" + +} + +// Redirect to the URL derived from the specified path, with specified status. +func (r *Redirect) To(location string) error { + r.c.setCanonical(HeaderLocation, location) + r.c.Status(r.status) + + return nil +} + +// Route redirects to the Route registered in the app with appropriate parameters. +// If you want to send queries or params to route, you should use config parameter. +func (r *Redirect) Route(name string, config ...RedirectConfig) error { + // Check config + cfg := RedirectConfig{} + if len(config) > 0 { + cfg = config[0] + } + + // Get location from route name + location, err := r.c.getLocationFromRoute(r.c.App().GetRoute(name), cfg.Params) + if err != nil { + return err + } + + // Flash messages + if len(r.messages) > 0 || len(r.oldInput) > 0 { + messageText := bytebufferpool.Get() + defer bytebufferpool.Put(messageText) + + // flash messages + for i, message := range r.messages { + _, _ = messageText.WriteString(message) + // when there are more messages or oldInput -> add a comma + if len(r.messages)-1 != i || (len(r.messages)-1 == i && len(r.oldInput) > 0) { + _, _ = messageText.WriteString(CookieDataSeparator) + } + } + r.messages = r.messages[:0] + + // old input data + i := 1 + for k, v := range r.oldInput { + _, _ = messageText.WriteString(OldInputDataPrefix + k + CookieDataAssigner + v) + if len(r.oldInput) != i { + _, _ = messageText.WriteString(CookieDataSeparator) + } + i++ + } + + r.c.Cookie(&Cookie{ + Name: FlashCookieName, + Value: r.c.app.getString(messageText.Bytes()), + SessionOnly: true, + }) + } + + // Check queries + if len(cfg.Queries) > 0 { + queryText := bytebufferpool.Get() + defer bytebufferpool.Put(queryText) + + i := 1 + for k, v := range cfg.Queries { + _, _ = queryText.WriteString(k + "=" + v) + + if i != len(cfg.Queries) { + _, _ = queryText.WriteString("&") + } + i++ + } + + return r.To(location + "?" + r.c.app.getString(queryText.Bytes())) + } + + return r.To(location) +} + +// Redirect back to the URL to referer. +func (r *Redirect) Back(fallback ...string) error { + location := r.c.Get(HeaderReferer) + if location == "" { + // Check fallback URL + if len(fallback) == 0 { + err := ErrRedirectBackNoFallback + r.c.Status(err.Code) + + return err + } + location = fallback[0] + } + + return r.To(location) +} + +// setFlash is a method to get flash messages before removing them +func (r *Redirect) setFlash() { + // parse flash messages + cookieValue := r.c.Cookies(FlashCookieName) + + var commaPos int + for { + commaPos = findNextNonEscapedCharsetPosition(cookieValue, []byte(CookieDataSeparator)) + if commaPos != -1 { + r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue[:commaPos], " ")) + cookieValue = cookieValue[commaPos+1:] + } else { + r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue, " ")) + break + } + } + + r.c.ClearCookie(FlashCookieName) +} + +func parseMessage(raw string) (key, value string) { + if i := findNextNonEscapedCharsetPosition(raw, []byte(CookieDataAssigner)); i != -1 { + return RemoveEscapeChar(raw[:i]), RemoveEscapeChar(raw[i+1:]) + } + return RemoveEscapeChar(raw), "" +} diff --git a/redirect_test.go b/redirect_test.go new file mode 100644 index 0000000000..f603c6fc43 --- /dev/null +++ b/redirect_test.go @@ -0,0 +1,528 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 📝 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "context" + "net" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttputil" +) + +// go test -run Test_Redirect_To +func Test_Redirect_To(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().To("http://default.com") + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "http://default.com", string(c.Response().Header.Peek(HeaderLocation))) + + c.Redirect().Status(301).To("http://example.com") + require.Equal(t, 301, c.Response().StatusCode()) + require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Route_WithParams +func Test_Redirect_Route_WithParams(t *testing.T) { + t.Parallel() + app := New() + app.Get("/user/:name", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "name": "fiber", + }, + }) + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Route_WithParams_WithQueries +func Test_Redirect_Route_WithParams_WithQueries(t *testing.T) { + t.Parallel() + app := New() + app.Get("/user/:name", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "name": "fiber", + }, + Queries: map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"}, + }) + require.Equal(t, 302, c.Response().StatusCode()) + // analysis of query parameters with url parsing, since a map pass is always randomly ordered + location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) + require.NoError(t, err, "url.Parse(location)") + require.Equal(t, "/user/fiber", location.Path) + require.Equal(t, url.Values{"data[0][name]": []string{"john"}, "data[0][age]": []string{"10"}, "test": []string{"doe"}}, location.Query()) +} + +// go test -run Test_Redirect_Route_WithOptionalParams +func Test_Redirect_Route_WithOptionalParams(t *testing.T) { + t.Parallel() + app := New() + app.Get("/user/:name?", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "name": "fiber", + }, + }) + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Route_WithOptionalParamsWithoutValue +func Test_Redirect_Route_WithOptionalParamsWithoutValue(t *testing.T) { + t.Parallel() + app := New() + app.Get("/user/:name?", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Route("user") + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user/", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Route_WithGreedyParameters +func Test_Redirect_Route_WithGreedyParameters(t *testing.T) { + t.Parallel() + app := New() + app.Get("/user/+", func(c Ctx) error { + return c.JSON(c.Params("+")) + }).Name("user") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "+": "test/routes", + }, + }) + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user/test/routes", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Back +func Test_Redirect_Back(t *testing.T) { + t.Parallel() + app := New() + app.Get("/", func(c Ctx) error { + return c.JSON("Home") + }).Name("home") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Redirect().Back("/") + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/", string(c.Response().Header.Peek(HeaderLocation))) + + err := c.Redirect().Back() + require.Equal(t, 500, c.Response().StatusCode()) + require.ErrorAs(t, ErrRedirectBackNoFallback, &err) +} + +// go test -run Test_Redirect_Back_WithReferer +func Test_Redirect_Back_WithReferer(t *testing.T) { + t.Parallel() + app := New() + app.Get("/", func(c Ctx) error { + return c.JSON("Home") + }).Name("home") + app.Get("/back", func(c Ctx) error { + return c.JSON("Back") + }).Name("back") + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Request().Header.Set(HeaderReferer, "/back") + c.Redirect().Back("/") + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/back", c.Get(HeaderReferer)) + require.Equal(t, "/back", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -run Test_Redirect_Route_WithFlashMessages +func Test_Redirect_Route_WithFlashMessages(t *testing.T) { + t.Parallel() + + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Redirect().With("success", "1").With("message", "test").Route("user") + + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) + + equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax" + require.True(t, equal) + + c.Redirect().setFlash() + require.Equal(t, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) +} + +// go test -run Test_Redirect_Route_WithOldInput +func Test_Redirect_Route_WithOldInput(t *testing.T) { + t.Parallel() + + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().URI().SetQueryString("id=1&name=tom") + c.Redirect().With("success", "1").With("message", "test").WithInput().Route("user") + + require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) + + require.Contains(t, c.GetRespHeader(HeaderSetCookie), "fiber_flash=") + require.Contains(t, c.GetRespHeader(HeaderSetCookie), "success:1") + require.Contains(t, c.GetRespHeader(HeaderSetCookie), "message:test") + + require.Contains(t, c.GetRespHeader(HeaderSetCookie), ",old_input_data_id:1") + require.Contains(t, c.GetRespHeader(HeaderSetCookie), ",old_input_data_name:tom") + + c.Redirect().setFlash() + require.Equal(t, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) +} + +// go test -run Test_Redirect_setFlash +func Test_Redirect_setFlash(t *testing.T) { + t.Parallel() + + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + + c.Redirect().setFlash() + + require.Equal(t, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + + require.Equal(t, "1", c.Redirect().Message("success")) + require.Equal(t, "test", c.Redirect().Message("message")) + require.Equal(t, map[string]string{"success": "1", "message": "test"}, c.Redirect().Messages()) + + require.Equal(t, "1", c.Redirect().OldInput("id")) + require.Equal(t, "tom", c.Redirect().OldInput("name")) + require.Equal(t, map[string]string{"id": "1", "name": "tom"}, c.Redirect().OldInputs()) +} + +// go test -run Test_Redirect_Request +func Test_Redirect_Request(t *testing.T) { + t.Parallel() + + app := New() + + app.Get("/", func(c Ctx) error { + return c.Redirect().With("key", "value").With("key2", "value2").With("co\\:m\\,ma", "Fi\\:ber\\, v3").Route("name") + }) + + app.Get("/with-inputs", func(c Ctx) error { + return c.Redirect().WithInput().With("key", "value").With("key2", "value2").Route("name") + }) + + app.Get("/just-inputs", func(c Ctx) error { + return c.Redirect().WithInput().Route("name") + }) + + app.Get("/redirected", func(c Ctx) error { + return c.JSON(Map{ + "messages": c.Redirect().Messages(), + "inputs": c.Redirect().OldInputs(), + }) + }).Name("name") + + // Start test server + ln := fasthttputil.NewInmemoryListener() + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond) + defer cancel() + + err := app.Listener(ln, ListenConfig{ + DisableStartupMessage: true, + GracefulContext: ctx, + }) + + require.NoError(t, err) + }() + + // Test cases + testCases := []struct { + URL string + CookieValue string + ExpectedBody string + ExpectedStatusCode int + ExceptedErrsLen int + }{ + { + URL: "/", + CookieValue: "key:value,key2:value2,co\\:m\\,ma:Fi\\:ber\\, v3", + ExpectedBody: `{"inputs":{},"messages":{"co:m,ma":"Fi:ber, v3","key":"value","key2":"value2"}}`, + ExpectedStatusCode: StatusOK, + ExceptedErrsLen: 0, + }, + { + URL: "/with-inputs?name=john&surname=doe", + CookieValue: "key:value,key2:value2,key:value,key2:value2,old_input_data_name:john,old_input_data_surname:doe", + ExpectedBody: `{"inputs":{"name":"john","surname":"doe"},"messages":{"key":"value","key2":"value2"}}`, + ExpectedStatusCode: StatusOK, + ExceptedErrsLen: 0, + }, + { + URL: "/just-inputs?name=john&surname=doe", + CookieValue: "old_input_data_name:john,old_input_data_surname:doe", + ExpectedBody: `{"inputs":{"name":"john","surname":"doe"},"messages":{}}`, + ExpectedStatusCode: StatusOK, + ExceptedErrsLen: 0, + }, + } + + for _, tc := range testCases { + a := Get("http://example.com" + tc.URL) + a.Cookie(FlashCookieName, tc.CookieValue) + a.MaxRedirectsCount(1) + a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } + code, body, errs := a.String() + + require.Equal(t, tc.ExpectedStatusCode, code) + require.Equal(t, tc.ExpectedBody, body) + require.Equal(t, tc.ExceptedErrsLen, len(errs)) + } +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_Route -benchmem -count=4 +func Benchmark_Redirect_Route(b *testing.B) { + app := New() + app.Get("/user/:name", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "name": "fiber", + }, + }) + } + + require.Equal(b, 302, c.Response().StatusCode()) + require.Equal(b, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_Route_WithQueries -benchmem -count=4 +func Benchmark_Redirect_Route_WithQueries(b *testing.B) { + app := New() + app.Get("/user/:name", func(c Ctx) error { + return c.JSON(c.Params("name")) + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + c.Redirect().Route("user", RedirectConfig{ + Params: Map{ + "name": "fiber", + }, + Queries: map[string]string{"a": "a", "b": "b"}, + }) + } + + require.Equal(b, 302, c.Response().StatusCode()) + // analysis of query parameters with url parsing, since a map pass is always randomly ordered + location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) + require.NoError(b, err, "url.Parse(location)") + require.Equal(b, "/user/fiber", location.Path) + require.Equal(b, url.Values{"a": []string{"a"}, "b": []string{"b"}}, location.Query()) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_Route_WithFlashMessages -benchmem -count=4 +func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + c.Redirect().With("success", "1").With("message", "test").Route("user") + } + + require.Equal(b, 302, c.Response().StatusCode()) + require.Equal(b, "/user", string(c.Response().Header.Peek(HeaderLocation))) + + equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax" + require.True(b, equal) + + c.Redirect().setFlash() + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_setFlash -benchmem -count=4 +func Benchmark_Redirect_setFlash(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + c.Redirect().setFlash() + } + + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + + require.Equal(b, "1", c.Redirect().Message("success")) + require.Equal(b, "test", c.Redirect().Message("message")) + require.Equal(b, map[string]string{"success": "1", "message": "test"}, c.Redirect().Messages()) + + require.Equal(b, "1", c.Redirect().OldInput("id")) + require.Equal(b, "tom", c.Redirect().OldInput("name")) + require.Equal(b, map[string]string{"id": "1", "name": "tom"}, c.Redirect().OldInputs()) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_Messages -benchmem -count=4 +func Benchmark_Redirect_Messages(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Redirect().setFlash() + + var msgs map[string]string + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + msgs = c.Redirect().Messages() + } + + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + require.Equal(b, map[string]string{"success": "1", "message": "test"}, msgs) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_OldInputs -benchmem -count=4 +func Benchmark_Redirect_OldInputs(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Redirect().setFlash() + + var oldInputs map[string]string + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + oldInputs = c.Redirect().OldInputs() + } + + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + require.Equal(b, map[string]string{"id": "1", "name": "tom"}, oldInputs) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_Message -benchmem -count=4 +func Benchmark_Redirect_Message(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Redirect().setFlash() + + var msg string + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + msg = c.Redirect().Message("message") + } + + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + require.Equal(b, "test", msg) +} + +// go test -v -run=^$ -bench=Benchmark_Redirect_OldInput -benchmem -count=4 +func Benchmark_Redirect_OldInput(b *testing.B) { + app := New() + app.Get("/user", func(c Ctx) error { + return c.SendString("user") + }).Name("user") + + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Redirect().setFlash() + + var input string + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + input = c.Redirect().OldInput("name") + } + + require.Equal(b, "fiber_flash=; expires=Tue, 10 Nov 2009 23:00:00 GMT", c.GetRespHeader(HeaderSetCookie)) + require.Equal(b, "tom", input) +} diff --git a/register.go b/register.go new file mode 100644 index 0000000000..9f0bf92d92 --- /dev/null +++ b/register.go @@ -0,0 +1,128 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +// Register defines all router handle interface generate by Route(). +type Register interface { + All(handlers ...Handler) Register + Get(handlers ...Handler) Register + Head(handlers ...Handler) Register + Post(handlers ...Handler) Register + Put(handlers ...Handler) Register + Delete(handlers ...Handler) Register + Connect(handlers ...Handler) Register + Options(handlers ...Handler) Register + Trace(handlers ...Handler) Register + Patch(handlers ...Handler) Register + + Add(method string, handlers ...Handler) Register + + Static(root string, config ...Static) Register + + Route(path string) Register +} + +var _ (Register) = (*Registering)(nil) + +// Registering struct +type Registering struct { + app *App + + path string +} + +// All registers a middleware route that will match requests +// with the provided path which is stored in register struct. +// +// app.Route("/").All(func(c fiber.Ctx) error { +// return c.Next() +// }) +// app.Route("/api").All(func(c fiber.Ctx) error { +// return c.Next() +// }) +// app.Route("/api").All(handler, func(c fiber.Ctx) error { +// return c.Next() +// }) +// +// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... +func (r *Registering) All(handlers ...Handler) Register { + r.app.register(methodUse, r.path, handlers...) + return r +} + +// Get registers a route for GET methods that requests a representation +// of the specified resource. Requests using GET should only retrieve data. +func (r *Registering) Get(handlers ...Handler) Register { + r.app.Add(MethodHead, r.path, handlers...).Add(MethodGet, r.path, handlers...) + return r +} + +// Head registers a route for HEAD methods that asks for a response identical +// to that of a GET request, but without the response body. +func (r *Registering) Head(handlers ...Handler) Register { + return r.Add(MethodHead, handlers...) +} + +// Post registers a route for POST methods that is used to submit an entity to the +// specified resource, often causing a change in state or side effects on the server. +func (r *Registering) Post(handlers ...Handler) Register { + return r.Add(MethodPost, handlers...) +} + +// Put registers a route for PUT methods that replaces all current representations +// of the target resource with the request payload. +func (r *Registering) Put(handlers ...Handler) Register { + return r.Add(MethodPut, handlers...) +} + +// Delete registers a route for DELETE methods that deletes the specified resource. +func (r *Registering) Delete(handlers ...Handler) Register { + return r.Add(MethodDelete, handlers...) +} + +// Connect registers a route for CONNECT methods that establishes a tunnel to the +// server identified by the target resource. +func (r *Registering) Connect(handlers ...Handler) Register { + return r.Add(MethodConnect, handlers...) +} + +// Options registers a route for OPTIONS methods that is used to describe the +// communication options for the target resource. +func (r *Registering) Options(handlers ...Handler) Register { + return r.Add(MethodOptions, handlers...) +} + +// Trace registers a route for TRACE methods that performs a message loop-back +// test along the r.Path to the target resource. +func (r *Registering) Trace(handlers ...Handler) Register { + return r.Add(MethodTrace, handlers...) +} + +// Patch registers a route for PATCH methods that is used to apply partial +// modifications to a resource. +func (r *Registering) Patch(handlers ...Handler) Register { + return r.Add(MethodPatch, handlers...) +} + +// Add allows you to specify a HTTP method to register a route +func (r *Registering) Add(method string, handlers ...Handler) Register { + r.app.register(method, r.path, handlers...) + return r +} + +// Static will create a file server serving static files +func (r *Registering) Static(root string, config ...Static) Register { + r.app.registerStatic(r.path, root, config...) + return r +} + +// Route returns a new Register instance whose route path takes +// the path in the current instance as its prefix. +func (r *Registering) Route(path string) Register { + // Create new group + route := &Registering{app: r.app, path: getGroupPath(r.path, path)} + + return route +} diff --git a/router.go b/router.go index 3c406271dd..3d0d23e367 100644 --- a/router.go +++ b/router.go @@ -12,13 +12,13 @@ import ( "sync/atomic" "time" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" "github.com/valyala/fasthttp" ) -// Router defines all router handle interface includes app and group router. +// Router defines all router handle interface, including app and group router. type Router interface { - Use(args ...interface{}) Router + Use(args ...any) Router Get(path string, handlers ...Handler) Router Head(path string, handlers ...Handler) Router @@ -36,12 +36,12 @@ type Router interface { Group(prefix string, handlers ...Handler) Router - Route(prefix string, fn func(router Router), name ...string) Router + Route(path string) Register Name(name string) Router } -// Route is a struct that holds all metadata for each registered handler +// Route is a struct that holds all metadata for each registered handler. type Route struct { // Data for routing pos uint32 // Position in stack -> important for the sort of the matched routes @@ -94,7 +94,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) (ma return false } -func (app *App) next(c CustomCtx, customCtx bool) (match bool, err error) { +func (app *App) nextCustom(c CustomCtx) (match bool, err error) { // Get stack length tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()] if !ok { @@ -134,22 +134,64 @@ func (app *App) next(c CustomCtx, customCtx bool) (match bool, err error) { // If c.Next() does not match, return 404 err = NewError(StatusNotFound, "Cannot "+c.Method()+" "+c.getPathOriginal()) - var isMethodExist bool - if customCtx { - isMethodExist = methodExistCustom(c) - } else { - isMethodExist = methodExist(c.(*DefaultCtx)) + // If no match, scan stack again if other methods match the request + // Moved from app.handler because middleware may break the route chain + if !c.getMatched() && methodExistCustom(c) { + err = ErrMethodNotAllowed + } + return +} + +func (app *App) next(c *DefaultCtx) (match bool, err error) { + // Get stack length + tree, ok := app.treeStack[c.methodINT][c.treePath] + if !ok { + tree = app.treeStack[c.methodINT][""] } + lenr := len(tree) - 1 + + // Loop over the route stack starting from previous index + for c.indexRoute < lenr { + // Increment route index + c.indexRoute++ + + // Get *Route + route := tree[c.indexRoute] + + // Check if it matches the request path + match = route.match(c.detectionPath, c.path, &c.values) + + // No match, next route + if !match { + continue + } + // Pass route reference and param values + c.route = route + + // Non use handler matched + if !c.matched && !route.use { + c.matched = true + } + + // Execute first handler of route + c.indexHandler = 0 + err = route.Handlers[0](c) + return match, err // Stop scanning the stack + } + + // If c.Next() does not match, return 404 + err = NewError(StatusNotFound, "Cannot "+c.method+" "+c.pathOriginal) // If no match, scan stack again if other methods match the request // Moved from app.handler because middleware may break the route chain - if !c.getMatched() && isMethodExist { + if !c.matched && methodExist(c) { err = ErrMethodNotAllowed } return } func (app *App) handler(rctx *fasthttp.RequestCtx) { + // Handler for default ctxs var c CustomCtx if app.newCtxFunc != nil { c = app.AcquireCtx().(CustomCtx) @@ -165,8 +207,18 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) { return } + // check flash messages + if strings.Contains(utils.UnsafeString(c.Request().Header.RawHeaders()), FlashCookieName) { + c.Redirect().setFlash() + } + // Find match in stack - _, err := app.next(c, app.newCtxFunc != nil) + var err error + if app.newCtxFunc != nil { + _, err = app.nextCustom(c) + } else { + _, err = app.next(c.(*DefaultCtx)) + } if err != nil { if catch := c.App().ErrorHandler(c, err); catch != nil { _ = c.SendStatus(StatusInternalServerError) @@ -186,7 +238,7 @@ func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { } // Strict routing, remove trailing slashes if !app.config.StrictRouting && len(prettyPath) > 1 { - prettyPath = utils.TrimRight(prettyPath, '/') + prettyPath = strings.TrimRight(prettyPath, "/") } route.Path = prefixedPath @@ -244,7 +296,7 @@ func (app *App) register(method, pathRaw string, handlers ...Handler) Router { } // Strict routing, remove trailing slashes if !app.config.StrictRouting && len(pathPretty) > 1 { - pathPretty = utils.TrimRight(pathPretty, '/') + pathPretty = strings.TrimRight(pathPretty, "/") } // Is layer a middleware? isUse := method == methodUse @@ -363,6 +415,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { // Set config if provided var cacheControlValue string + var modifyResponse Handler if len(config) > 0 { maxAge := config[0].MaxAge if maxAge > 0 { @@ -375,6 +428,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { if config[0].Index != "" { fs.IndexNames = []string{config[0].Index} } + modifyResponse = config[0].ModifyResponse } fileHandler := fs.NewRequestHandler() handler := func(c Ctx) error { @@ -394,6 +448,9 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { if len(cacheControlValue) > 0 { c.Context().Response.Header.Set(HeaderCacheControl, cacheControlValue) } + if modifyResponse != nil { + return modifyResponse(c) + } return nil } // Reset response to default @@ -425,7 +482,13 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { return app } -func (app *App) addRoute(method string, route *Route) { +func (app *App) addRoute(method string, route *Route, isMounted ...bool) { + // Check mounted routes + var mounted bool + if len(isMounted) > 0 { + mounted = isMounted[0] + } + // Get unique HTTP method identifier m := methodInt(method) @@ -443,12 +506,15 @@ func (app *App) addRoute(method string, route *Route) { app.routesRefreshed = true } - app.mutex.Lock() - app.latestRoute = route - if err := app.hooks.executeOnRouteHooks(*route); err != nil { - panic(err) + // Execute onRoute hooks & change latestRoute if not adding mounted route + if !mounted { + app.mutex.Lock() + app.latestRoute = route + if err := app.hooks.executeOnRouteHooks(*route); err != nil { + panic(err) + } + app.mutex.Unlock() } - app.mutex.Unlock() } // buildTree build the prefix tree from the previously registered routes @@ -456,6 +522,7 @@ func (app *App) buildTree() *App { if !app.routesRefreshed { return app } + // loop all the methods and stacks and create the prefix tree for m := range intMethod { tsMap := make(map[string][]*Route) @@ -469,6 +536,7 @@ func (app *App) buildTree() *App { } app.treeStack[m] = tsMap } + // loop the methods and tree stacks and add global stack and sort everything for m := range intMethod { tsMap := app.treeStack[m] diff --git a/router_test.go b/router_test.go index 7d56072d54..9692fc9bae 100644 --- a/router_test.go +++ b/router_test.go @@ -16,7 +16,8 @@ import ( "strings" "testing" - "github.com/gofiber/fiber/v3/utils" + "github.com/gofiber/utils" + "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) @@ -40,21 +41,21 @@ func Test_Route_Match_SameLength(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/:param", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, ":param", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, ":param", app.getString(body)) // with param resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "test", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "test", app.getString(body)) } func Test_Route_Match_Star(t *testing.T) { @@ -65,21 +66,21 @@ func Test_Route_Match_Star(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/*", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "*", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "*", app.getString(body)) // with param resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "test", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "test", app.getString(body)) // without parameter route := Route{ @@ -89,18 +90,18 @@ func Test_Route_Match_Star(t *testing.T) { } params := [maxParams]string{} match := route.match("", "", ¶ms) - utils.AssertEqual(t, true, match) - utils.AssertEqual(t, [maxParams]string{}, params) + require.True(t, match) + require.Equal(t, [maxParams]string{}, params) // with parameter match = route.match("/favicon.ico", "/favicon.ico", ¶ms) - utils.AssertEqual(t, true, match) - utils.AssertEqual(t, [maxParams]string{"favicon.ico"}, params) + require.True(t, match) + require.Equal(t, [maxParams]string{"favicon.ico"}, params) // without parameter again match = route.match("", "", ¶ms) - utils.AssertEqual(t, true, match) - utils.AssertEqual(t, [maxParams]string{}, params) + require.True(t, match) + require.Equal(t, [maxParams]string{}, params) } func Test_Route_Match_Root(t *testing.T) { @@ -111,12 +112,12 @@ func Test_Route_Match_Root(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "root", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "root", app.getString(body)) } func Test_Route_Match_Parser(t *testing.T) { @@ -129,21 +130,21 @@ func Test_Route_Match_Parser(t *testing.T) { return c.SendString(c.Params("*")) }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/foo/bar", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "bar", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "bar", app.getString(body)) // with star resp, err = app.Test(httptest.NewRequest(MethodGet, "/Foobar/test", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "test", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "test", app.getString(body)) } func Test_Route_Match_Middleware(t *testing.T) { @@ -154,21 +155,21 @@ func Test_Route_Match_Middleware(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/foo/*", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "*", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "*", app.getString(body)) // with param resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo/bar/fasel", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "bar/fasel", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "bar/fasel", app.getString(body)) } func Test_Route_Match_UnescapedPath(t *testing.T) { @@ -179,22 +180,22 @@ func Test_Route_Match_UnescapedPath(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/cr%C3%A9er", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "test", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "test", app.getString(body)) // without special chars resp, err = app.Test(httptest.NewRequest(MethodGet, "/créer", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") // check deactivated behavior app.config.UnescapePath = false resp, err = app.Test(httptest.NewRequest(MethodGet, "/cr%C3%A9er", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") } func Test_Route_Match_WithEscapeChar(t *testing.T) { @@ -215,30 +216,30 @@ func Test_Route_Match_WithEscapeChar(t *testing.T) { // check static route resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/some/resource/name:customVerb", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "static", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "static", app.getString(body)) // check group route resp, err = app.Test(httptest.NewRequest(MethodGet, "/v2/:firstVerb/:customVerb", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "group", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "group", app.getString(body)) // check param route resp, err = app.Test(httptest.NewRequest(MethodGet, "/v3/awesome/name:customVerb", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "awesome", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "awesome", app.getString(body)) } func Test_Route_Match_Middleware_HasPrefix(t *testing.T) { @@ -249,12 +250,12 @@ func Test_Route_Match_Middleware_HasPrefix(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/foo/bar", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "middleware", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "middleware", app.getString(body)) } func Test_Route_Match_Middleware_Root(t *testing.T) { @@ -265,19 +266,19 @@ func Test_Route_Match_Middleware_Root(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/everything", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, "middleware", app.getString(body)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, "middleware", app.getString(body)) } func Test_Router_Register_Missing_Handler(t *testing.T) { app := New() defer func() { if err := recover(); err != nil { - utils.AssertEqual(t, "missing handler in route: /doe\n", fmt.Sprintf("%v", err)) + require.Equal(t, "missing handler in route: /doe\n", fmt.Sprintf("%v", err)) } }() app.register("USE", "/doe") @@ -286,11 +287,11 @@ func Test_Router_Register_Missing_Handler(t *testing.T) { func Test_Ensure_Router_Interface_Implementation(t *testing.T) { var app any = (*App)(nil) _, ok := app.(Router) - utils.AssertEqual(t, true, ok) + require.True(t, ok) var group any = (*Group)(nil) _, ok = group.(Router) - utils.AssertEqual(t, true, ok) + require.True(t, ok) } func Test_Router_Handler_Catch_Error(t *testing.T) { @@ -307,7 +308,7 @@ func Test_Router_Handler_Catch_Error(t *testing.T) { app.Handler()(c) - utils.AssertEqual(t, StatusInternalServerError, c.Response.Header.StatusCode()) + require.Equal(t, StatusInternalServerError, c.Response.Header.StatusCode()) } func Test_Route_Static_Root(t *testing.T) { @@ -318,31 +319,31 @@ func Test_Route_Static_Root(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/style.css", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) app = New() app.Static("/", dir) resp, err = app.Test(httptest.NewRequest(MethodGet, "/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/style.css", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) } func Test_Route_Static_HasPrefix(t *testing.T) { @@ -353,20 +354,20 @@ func Test_Route_Static_HasPrefix(t *testing.T) { }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/static", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") 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") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err := io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) app = New() app.Static("/static/", dir, Static{ @@ -374,58 +375,58 @@ func Test_Route_Static_HasPrefix(t *testing.T) { }) resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 200, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") 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") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) app = New() app.Static("/static", dir) resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") 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") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) app = New() app.Static("/static/", dir) resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil)) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, 404, resp.StatusCode, "Status code") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 404, resp.StatusCode, "Status code") 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") + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") body, err = io.ReadAll(resp.Body) - utils.AssertEqual(t, nil, err, "app.Test(req)") - utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color")) + require.NoError(t, err, "app.Test(req)") + require.True(t, strings.Contains(app.getString(body), "color")) } ////////////////////////////////////////////// @@ -460,9 +461,9 @@ func Benchmark_App_MethodNotAllowed(b *testing.B) { appHandler(c) } b.StopTimer() - utils.AssertEqual(b, 405, c.Response.StatusCode()) - utils.AssertEqual(b, "GET, HEAD", string(c.Response.Header.Peek("Allow"))) - utils.AssertEqual(b, utils.StatusMessage(StatusMethodNotAllowed), string(c.Response.Body())) + require.Equal(b, 405, c.Response.StatusCode()) + require.Equal(b, "GET", string(c.Response.Header.Peek("Allow"))) + require.Equal(b, utils.StatusMessage(StatusMethodNotAllowed), string(c.Response.Body())) } // go test -v ./... -run=^$ -bench=Benchmark_Router_NotFound -benchmem -count=4 @@ -482,8 +483,8 @@ func Benchmark_Router_NotFound(b *testing.B) { for n := 0; n < b.N; n++ { appHandler(c) } - utils.AssertEqual(b, 404, c.Response.StatusCode()) - utils.AssertEqual(b, "Cannot DELETE /this/route/does/not/exist", string(c.Response.Body())) + require.Equal(b, 404, c.Response.StatusCode()) + require.Equal(b, "Cannot DELETE /this/route/does/not/exist", string(c.Response.Body())) } // go test -v ./... -run=^$ -bench=Benchmark_Router_Handler -benchmem -count=4 @@ -595,11 +596,11 @@ func Benchmark_Router_Next(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { c.indexRoute = -1 - res, err = app.next(c, false) + res, err = app.next(c) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, true, res) - utils.AssertEqual(b, 4, c.indexRoute) + require.NoError(b, err) + require.True(b, res) + require.Equal(b, 4, c.indexRoute) } // go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4 @@ -627,8 +628,8 @@ func Benchmark_Route_Match(b *testing.B) { match = route.match("/user/keys/1337", "/user/keys/1337", ¶ms) } - utils.AssertEqual(b, true, match) - utils.AssertEqual(b, []string{"1337"}, params[0:len(parsed.params)]) + require.True(b, match) + require.Equal(b, []string{"1337"}, params[0:len(parsed.params)]) } // go test -v ./... -run=^$ -bench=Benchmark_Route_Match_Star -benchmem -count=4 @@ -657,8 +658,8 @@ func Benchmark_Route_Match_Star(b *testing.B) { match = route.match("/user/keys/bla", "/user/keys/bla", ¶ms) } - utils.AssertEqual(b, true, match) - utils.AssertEqual(b, []string{"user/keys/bla"}, params[0:len(parsed.params)]) + require.True(b, match) + require.Equal(b, []string{"user/keys/bla"}, params[0:len(parsed.params)]) } // go test -v ./... -run=^$ -bench=Benchmark_Route_Match_Root -benchmem -count=4 @@ -688,8 +689,8 @@ func Benchmark_Route_Match_Root(b *testing.B) { match = route.match("/", "/", ¶ms) } - utils.AssertEqual(b, true, match) - utils.AssertEqual(b, []string{}, params[0:len(parsed.params)]) + require.True(b, match) + require.Equal(b, []string{}, params[0:len(parsed.params)]) } // go test -v ./... -run=^$ -bench=Benchmark_Router_Handler_CaseSensitive -benchmem -count=4 @@ -771,15 +772,15 @@ func Benchmark_Router_Github_API(b *testing.B) { for n := 0; n < b.N; n++ { c.URI().SetPath(routesFixture.TestRoutes[i].Path) - ctx := app.AcquireCtx().(CustomCtx) + ctx := app.AcquireCtx().(*DefaultCtx) ctx.Reset(c) - match, err = app.next(ctx, false) + match, err = app.next(ctx) app.ReleaseCtx(ctx) } - utils.AssertEqual(b, nil, err) - utils.AssertEqual(b, true, match) + require.NoError(b, err) + require.True(b, match) } } diff --git a/utils/README.md b/utils/README.md deleted file mode 100644 index 0276ff380e..0000000000 --- a/utils/README.md +++ /dev/null @@ -1,90 +0,0 @@ -A collection of common functions but with better performance, less allocations and no dependencies created for [Fiber](https://github.com/gofiber/fiber). - -```go -// go test -benchmem -run=^$ -bench=Benchmark_ -count=2 - -Benchmark_ToLowerBytes/fiber-16 42847654 25.7 ns/op 0 B/op 0 allocs/op -Benchmark_ToLowerBytes/fiber-16 46143196 25.7 ns/op 0 B/op 0 allocs/op -Benchmark_ToLowerBytes/default-16 17387322 67.4 ns/op 48 B/op 1 allocs/op -Benchmark_ToLowerBytes/default-16 17906491 67.4 ns/op 48 B/op 1 allocs/op - -Benchmark_ToUpperBytes/fiber-16 46143729 25.7 ns/op 0 B/op 0 allocs/op -Benchmark_ToUpperBytes/fiber-16 47989250 25.6 ns/op 0 B/op 0 allocs/op -Benchmark_ToUpperBytes/default-16 15580854 76.7 ns/op 48 B/op 1 allocs/op -Benchmark_ToUpperBytes/default-16 15381202 76.9 ns/op 48 B/op 1 allocs/op - -Benchmark_TrimRightBytes/fiber-16 70572459 16.3 ns/op 8 B/op 1 allocs/op -Benchmark_TrimRightBytes/fiber-16 74983597 16.3 ns/op 8 B/op 1 allocs/op -Benchmark_TrimRightBytes/default-16 16212578 74.1 ns/op 40 B/op 2 allocs/op -Benchmark_TrimRightBytes/default-16 16434686 74.1 ns/op 40 B/op 2 allocs/op - -Benchmark_TrimLeftBytes/fiber-16 74983128 16.3 ns/op 8 B/op 1 allocs/op -Benchmark_TrimLeftBytes/fiber-16 74985002 16.3 ns/op 8 B/op 1 allocs/op -Benchmark_TrimLeftBytes/default-16 21047868 56.5 ns/op 40 B/op 2 allocs/op -Benchmark_TrimLeftBytes/default-16 21048015 56.5 ns/op 40 B/op 2 allocs/op - -Benchmark_TrimBytes/fiber-16 54533307 21.9 ns/op 16 B/op 1 allocs/op -Benchmark_TrimBytes/fiber-16 54532812 21.9 ns/op 16 B/op 1 allocs/op -Benchmark_TrimBytes/default-16 14282517 84.6 ns/op 48 B/op 2 allocs/op -Benchmark_TrimBytes/default-16 14114508 84.7 ns/op 48 B/op 2 allocs/op - -Benchmark_EqualFolds/fiber-16 36355153 32.6 ns/op 0 B/op 0 allocs/op -Benchmark_EqualFolds/fiber-16 36355593 32.6 ns/op 0 B/op 0 allocs/op -Benchmark_EqualFolds/default-16 15186220 78.1 ns/op 0 B/op 0 allocs/op -Benchmark_EqualFolds/default-16 15186412 78.3 ns/op 0 B/op 0 allocs/op - -Benchmark_UUID/fiber-16 23994625 49.8 ns/op 48 B/op 1 allocs/op -Benchmark_UUID/fiber-16 23994768 50.1 ns/op 48 B/op 1 allocs/op -Benchmark_UUID/default-16 3233772 371 ns/op 208 B/op 6 allocs/op -Benchmark_UUID/default-16 3251295 370 ns/op 208 B/op 6 allocs/op - -Benchmark_GetString/unsafe-16 1000000000 0.709 ns/op 0 B/op 0 allocs/op -Benchmark_GetString/unsafe-16 1000000000 0.713 ns/op 0 B/op 0 allocs/op -Benchmark_GetString/default-16 59986202 19.0 ns/op 16 B/op 1 allocs/op -Benchmark_GetString/default-16 63142939 19.0 ns/op 16 B/op 1 allocs/op - -Benchmark_GetBytes/unsafe-16 508360195 2.36 ns/op 0 B/op 0 allocs/op -Benchmark_GetBytes/unsafe-16 508359979 2.35 ns/op 0 B/op 0 allocs/op -Benchmark_GetBytes/default-16 46143019 25.7 ns/op 16 B/op 1 allocs/op -Benchmark_GetBytes/default-16 44434734 25.6 ns/op 16 B/op 1 allocs/op - -Benchmark_GetMIME/fiber-16 21423750 56.3 ns/op 0 B/op 0 allocs/op -Benchmark_GetMIME/fiber-16 21423559 55.4 ns/op 0 B/op 0 allocs/op -Benchmark_GetMIME/default-16 6735282 173 ns/op 0 B/op 0 allocs/op -Benchmark_GetMIME/default-16 6895002 172 ns/op 0 B/op 0 allocs/op - -Benchmark_StatusMessage/fiber-16 1000000000 0.766 ns/op 0 B/op 0 allocs/op -Benchmark_StatusMessage/fiber-16 1000000000 0.767 ns/op 0 B/op 0 allocs/op -Benchmark_StatusMessage/default-16 159538528 7.50 ns/op 0 B/op 0 allocs/op -Benchmark_StatusMessage/default-16 159750830 7.51 ns/op 0 B/op 0 allocs/op - -Benchmark_ToUpper/fiber-16 22217408 53.3 ns/op 48 B/op 1 allocs/op -Benchmark_ToUpper/fiber-16 22636554 53.2 ns/op 48 B/op 1 allocs/op -Benchmark_ToUpper/default-16 11108600 108 ns/op 48 B/op 1 allocs/op -Benchmark_ToUpper/default-16 11108580 108 ns/op 48 B/op 1 allocs/op - -Benchmark_ToLower/fiber-16 23994720 49.8 ns/op 48 B/op 1 allocs/op -Benchmark_ToLower/fiber-16 23994768 50.1 ns/op 48 B/op 1 allocs/op -Benchmark_ToLower/default-16 10808376 110 ns/op 48 B/op 1 allocs/op -Benchmark_ToLower/default-16 10617034 110 ns/op 48 B/op 1 allocs/op - -Benchmark_TrimRight/fiber-16 413699521 2.94 ns/op 0 B/op 0 allocs/op -Benchmark_TrimRight/fiber-16 415131687 2.91 ns/op 0 B/op 0 allocs/op -Benchmark_TrimRight/default-16 23994577 49.1 ns/op 32 B/op 1 allocs/op -Benchmark_TrimRight/default-16 24484249 49.4 ns/op 32 B/op 1 allocs/op - -Benchmark_TrimLeft/fiber-16 379661170 3.13 ns/op 0 B/op 0 allocs/op -Benchmark_TrimLeft/fiber-16 382079941 3.16 ns/op 0 B/op 0 allocs/op -Benchmark_TrimLeft/default-16 27900877 41.9 ns/op 32 B/op 1 allocs/op -Benchmark_TrimLeft/default-16 28564898 42.0 ns/op 32 B/op 1 allocs/op - -Benchmark_Trim/fiber-16 236632856 4.96 ns/op 0 B/op 0 allocs/op -Benchmark_Trim/fiber-16 237570085 4.93 ns/op 0 B/op 0 allocs/op -Benchmark_Trim/default-16 18457221 66.0 ns/op 32 B/op 1 allocs/op -Benchmark_Trim/default-16 18177328 65.9 ns/op 32 B/op 1 allocs/op -Benchmark_Trim/default.trimspace-16 188933770 6.33 ns/op 0 B/op 0 allocs/op -Benchmark_Trim/default.trimspace-16 184007649 6.42 ns/op 0 B/op 0 allocs/op - -Benchmark_ConvertToBytes/fiber-8 43773547 24.43 ns/op 0 B/op 0 allocs/op -Benchmark_ConvertToBytes/fiber-8 45849477 25.33 ns/op 0 B/op 0 allocs/op -``` diff --git a/utils/assertions.go b/utils/assertions.go deleted file mode 100644 index d124ad9e60..0000000000 --- a/utils/assertions.go +++ /dev/null @@ -1,67 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "bytes" - "fmt" - "log" - "path/filepath" - "reflect" - "runtime" - "testing" - "text/tabwriter" -) - -// AssertEqual checks if values are equal -func AssertEqual(tb testing.TB, expected, actual any, description ...string) { - if tb != nil { - tb.Helper() - } - - if reflect.DeepEqual(expected, actual) { - return - } - - aType := "" - bType := "" - - if expected != nil { - aType = reflect.TypeOf(expected).String() - } - if actual != nil { - bType = reflect.TypeOf(actual).String() - } - - testName := "AssertEqual" - if tb != nil { - testName = tb.Name() - } - - _, file, line, _ := runtime.Caller(1) - - var buf bytes.Buffer - w := tabwriter.NewWriter(&buf, 0, 0, 5, ' ', 0) - fmt.Fprintf(w, "\nTest:\t%s", testName) - fmt.Fprintf(w, "\nTrace:\t%s:%d", filepath.Base(file), line) - if len(description) > 0 { - fmt.Fprintf(w, "\nDescription:\t%s", description[0]) - } - fmt.Fprintf(w, "\nExpect:\t%v\t(%s)", expected, aType) - fmt.Fprintf(w, "\nResult:\t%v\t(%s)", actual, bType) - - result := "" - if err := w.Flush(); err != nil { - result = err.Error() - } else { - result = buf.String() - } - - if tb != nil { - tb.Fatal(result) - } else { - log.Fatal(result) - } -} diff --git a/utils/assertions_test.go b/utils/assertions_test.go deleted file mode 100644 index d2d32dc0cf..0000000000 --- a/utils/assertions_test.go +++ /dev/null @@ -1,13 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import "testing" - -func Test_AssertEqual(t *testing.T) { - t.Parallel() - AssertEqual(nil, []string{}, []string{}) - AssertEqual(t, []string{}, []string{}) -} diff --git a/utils/bytes.go b/utils/bytes.go deleted file mode 100644 index 49f55b2de7..0000000000 --- a/utils/bytes.go +++ /dev/null @@ -1,69 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -// ToLowerBytes converts ascii slice to lower-case -func ToLowerBytes(b []byte) []byte { - for i := 0; i < len(b); i++ { - b[i] = toLowerTable[b[i]] - } - return b -} - -// ToUpperBytes converts ascii slice to upper-case -func ToUpperBytes(b []byte) []byte { - for i := 0; i < len(b); i++ { - b[i] = toUpperTable[b[i]] - } - return b -} - -// TrimRightBytes is the equivalent of bytes.TrimRight -func TrimRightBytes(b []byte, cutset byte) []byte { - lenStr := len(b) - for lenStr > 0 && b[lenStr-1] == cutset { - lenStr-- - } - return b[:lenStr] -} - -// TrimLeftBytes is the equivalent of bytes.TrimLeft -func TrimLeftBytes(b []byte, cutset byte) []byte { - lenStr, start := len(b), 0 - for start < lenStr && b[start] == cutset { - start++ - } - return b[start:] -} - -// TrimBytes is the equivalent of bytes.Trim -func TrimBytes(b []byte, cutset byte) []byte { - i, j := 0, len(b)-1 - for ; i <= j; i++ { - if b[i] != cutset { - break - } - } - for ; i < j; j-- { - if b[j] != cutset { - break - } - } - - return b[i : j+1] -} - -// EqualFoldBytes tests ascii slices for equality case-insensitively -func EqualFoldBytes(b, s []byte) bool { - if len(b) != len(s) { - return false - } - for i := len(b) - 1; i >= 0; i-- { - if toUpperTable[b[i]] != toUpperTable[s[i]] { - return false - } - } - return true -} diff --git a/utils/bytes_test.go b/utils/bytes_test.go deleted file mode 100644 index a449856db1..0000000000 --- a/utils/bytes_test.go +++ /dev/null @@ -1,218 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "bytes" - "testing" -) - -func Test_ToLowerBytes(t *testing.T) { - t.Parallel() - res := ToLowerBytes([]byte("/MY/NAME/IS/:PARAM/*")) - AssertEqual(t, true, bytes.Equal([]byte("/my/name/is/:param/*"), res)) - res = ToLowerBytes([]byte("/MY1/NAME/IS/:PARAM/*")) - AssertEqual(t, true, bytes.Equal([]byte("/my1/name/is/:param/*"), res)) - res = ToLowerBytes([]byte("/MY2/NAME/IS/:PARAM/*")) - AssertEqual(t, true, bytes.Equal([]byte("/my2/name/is/:param/*"), res)) - res = ToLowerBytes([]byte("/MY3/NAME/IS/:PARAM/*")) - AssertEqual(t, true, bytes.Equal([]byte("/my3/name/is/:param/*"), res)) - res = ToLowerBytes([]byte("/MY4/NAME/IS/:PARAM/*")) - AssertEqual(t, true, bytes.Equal([]byte("/my4/name/is/:param/*"), res)) -} - -func Benchmark_ToLowerBytes(b *testing.B) { - path := []byte(largeStr) - want := []byte(lowerStr) - var res []byte - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLowerBytes(path) - } - AssertEqual(b, bytes.Equal(want, res), true) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.ToLower(path) - } - AssertEqual(b, bytes.Equal(want, res), true) - }) -} - -func Test_ToUpperBytes(t *testing.T) { - t.Parallel() - res := ToUpperBytes([]byte("/my/name/is/:param/*")) - AssertEqual(t, true, bytes.Equal([]byte("/MY/NAME/IS/:PARAM/*"), res)) - res = ToUpperBytes([]byte("/my1/name/is/:param/*")) - AssertEqual(t, true, bytes.Equal([]byte("/MY1/NAME/IS/:PARAM/*"), res)) - res = ToUpperBytes([]byte("/my2/name/is/:param/*")) - AssertEqual(t, true, bytes.Equal([]byte("/MY2/NAME/IS/:PARAM/*"), res)) - res = ToUpperBytes([]byte("/my3/name/is/:param/*")) - AssertEqual(t, true, bytes.Equal([]byte("/MY3/NAME/IS/:PARAM/*"), res)) - res = ToUpperBytes([]byte("/my4/name/is/:param/*")) - AssertEqual(t, true, bytes.Equal([]byte("/MY4/NAME/IS/:PARAM/*"), res)) -} - -func Benchmark_ToUpperBytes(b *testing.B) { - path := []byte(largeStr) - want := []byte(upperStr) - var res []byte - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToUpperBytes(path) - } - AssertEqual(b, bytes.Equal(want, res), true) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.ToUpper(path) - } - AssertEqual(b, bytes.Equal(want, res), true) - }) -} - -func Test_TrimRightBytes(t *testing.T) { - t.Parallel() - res := TrimRightBytes([]byte("/test//////"), '/') - AssertEqual(t, []byte("/test"), res) - - res = TrimRightBytes([]byte("/test"), '/') - AssertEqual(t, []byte("/test"), res) - - res = TrimRightBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimRightBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimRightBytes([]byte(""), ' ') - AssertEqual(t, 0, len(res)) -} - -func Benchmark_TrimRightBytes(b *testing.B) { - var res []byte - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = TrimRightBytes([]byte("foobar "), ' ') - } - AssertEqual(b, []byte("foobar"), res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.TrimRight([]byte("foobar "), " ") - } - AssertEqual(b, []byte("foobar"), res) - }) -} - -func Test_TrimLeftBytes(t *testing.T) { - t.Parallel() - res := TrimLeftBytes([]byte("////test/"), '/') - AssertEqual(t, []byte("test/"), res) - - res = TrimLeftBytes([]byte("test/"), '/') - AssertEqual(t, []byte("test/"), res) - - res = TrimLeftBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimLeftBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimLeftBytes([]byte(""), ' ') - AssertEqual(t, 0, len(res)) -} - -func Benchmark_TrimLeftBytes(b *testing.B) { - var res []byte - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = TrimLeftBytes([]byte(" foobar"), ' ') - } - AssertEqual(b, []byte("foobar"), res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.TrimLeft([]byte(" foobar"), " ") - } - AssertEqual(b, []byte("foobar"), res) - }) -} - -func Test_TrimBytes(t *testing.T) { - t.Parallel() - res := TrimBytes([]byte(" test "), ' ') - AssertEqual(t, []byte("test"), res) - - res = TrimBytes([]byte("test"), ' ') - AssertEqual(t, []byte("test"), res) - - res = TrimBytes([]byte(".test"), '.') - AssertEqual(t, []byte("test"), res) - - res = TrimBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimBytes([]byte(" "), ' ') - AssertEqual(t, 0, len(res)) - - res = TrimBytes([]byte(""), ' ') - AssertEqual(t, 0, len(res)) -} - -func Benchmark_TrimBytes(b *testing.B) { - var res []byte - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = TrimBytes([]byte(" foobar "), ' ') - } - AssertEqual(b, []byte("foobar"), res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.Trim([]byte(" foobar "), " ") - } - AssertEqual(b, []byte("foobar"), res) - }) -} - -func Benchmark_EqualFoldBytes(b *testing.B) { - left := []byte(upperStr) - right := []byte(lowerStr) - var res bool - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = EqualFoldBytes(left, right) - } - AssertEqual(b, true, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = bytes.EqualFold(left, right) - } - AssertEqual(b, true, res) - }) -} - -func Test_EqualFoldBytes(t *testing.T) { - t.Parallel() - res := EqualFoldBytes([]byte("/MY/NAME/IS/:PARAM/*"), []byte("/my/name/is/:param/*")) - AssertEqual(t, true, res) - res = EqualFoldBytes([]byte("/MY1/NAME/IS/:PARAM/*"), []byte("/MY1/NAME/IS/:PARAM/*")) - AssertEqual(t, true, res) - res = EqualFoldBytes([]byte("/my2/name/is/:param/*"), []byte("/my2/name")) - AssertEqual(t, false, res) - res = EqualFoldBytes([]byte("/dddddd"), []byte("eeeeee")) - AssertEqual(t, false, res) - res = EqualFoldBytes([]byte("\na"), []byte("*A")) - AssertEqual(t, false, res) - res = EqualFoldBytes([]byte("/MY3/NAME/IS/:PARAM/*"), []byte("/my3/name/is/:param/*")) - AssertEqual(t, true, res) - res = EqualFoldBytes([]byte("/MY4/NAME/IS/:PARAM/*"), []byte("/my4/nAME/IS/:param/*")) - AssertEqual(t, true, res) -} diff --git a/utils/common.go b/utils/common.go deleted file mode 100644 index 3fa4b05045..0000000000 --- a/utils/common.go +++ /dev/null @@ -1,155 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "encoding/hex" - "math" - "net" - "os" - "reflect" - "runtime" - "strconv" - "sync" - "sync/atomic" - "unicode" - - "github.com/google/uuid" -) - -const ( - toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" - toUpperTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" -) - -// Copyright © 2014, Roger Peppe -// github.com/rogpeppe/fastuuid -// All rights reserved. - -var ( - uuidSeed [24]byte - uuidCounter uint64 - uuidSetup sync.Once - unitsSlice = []byte("kmgtp") -) - -// UUID generates an universally unique identifier (UUID) -func UUID() string { - // Setup seed & counter once - uuidSetup.Do(func() { - if _, err := rand.Read(uuidSeed[:]); err != nil { - return - } - uuidCounter = binary.LittleEndian.Uint64(uuidSeed[:8]) - }) - if atomic.LoadUint64(&uuidCounter) <= 0 { - return "00000000-0000-0000-0000-000000000000" - } - // first 8 bytes differ, taking a slice of the first 16 bytes - x := atomic.AddUint64(&uuidCounter, 1) - uuid := uuidSeed - binary.LittleEndian.PutUint64(uuid[:8], x) - uuid[6], uuid[9] = uuid[9], uuid[6] - - // RFC4122 v4 - uuid[6] = (uuid[6] & 0x0f) | 0x40 - uuid[8] = uuid[8]&0x3f | 0x80 - - // create UUID representation of the first 128 bits - b := make([]byte, 36) - hex.Encode(b[0:8], uuid[0:4]) - b[8] = '-' - hex.Encode(b[9:13], uuid[4:6]) - b[13] = '-' - hex.Encode(b[14:18], uuid[6:8]) - b[18] = '-' - hex.Encode(b[19:23], uuid[8:10]) - b[23] = '-' - hex.Encode(b[24:], uuid[10:16]) - - return UnsafeString(b) -} - -// UUIDv4 returns a Random (Version 4) UUID. -// The strength of the UUIDs is based on the strength of the crypto/rand package. -func UUIDv4() string { - token, err := uuid.NewRandom() - if err != nil { - return UUID() - } - return token.String() -} - -// FunctionName returns function name -func FunctionName(fn any) string { - t := reflect.ValueOf(fn).Type() - if t.Kind() == reflect.Func { - return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() - } - return t.String() -} - -// GetArgument check if key is in arguments -func GetArgument(arg string) bool { - for i := range os.Args[1:] { - if os.Args[1:][i] == arg { - return true - } - } - return false -} - -// IncrementIPRange Find available next IP address -func IncrementIPRange(ip net.IP) { - for j := len(ip) - 1; j >= 0; j-- { - ip[j]++ - if ip[j] > 0 { - break - } - } -} - -// ConvertToBytes returns integer size of bytes from human-readable string, ex. 42kb, 42M -// Returns 0 if string is unrecognized -func ConvertToBytes(humanReadableString string) int { - strLen := len(humanReadableString) - if strLen == 0 { - return 0 - } - var unitPrefixPos, lastNumberPos int - // loop the string - for i := strLen - 1; i >= 0; i-- { - // check if the char is a number - if unicode.IsDigit(rune(humanReadableString[i])) { - lastNumberPos = i - break - } else if humanReadableString[i] != ' ' { - unitPrefixPos = i - } - } - - if lastNumberPos < 0 { - return 0 - } - // fetch the number part and parse it to float - size, err := strconv.ParseFloat(humanReadableString[:lastNumberPos+1], 64) - if err != nil { - return 0 - } - - // check the multiplier from the string and use it - if unitPrefixPos > 0 { - // convert multiplier char to lowercase and check if exists in units slice - index := bytes.IndexByte(unitsSlice, toLowerTable[humanReadableString[unitPrefixPos]]) - if index != -1 { - size *= math.Pow(1000, float64(index+1)) - } - } - - return int(size) -} diff --git a/utils/common_test.go b/utils/common_test.go deleted file mode 100644 index bf108d24e9..0000000000 --- a/utils/common_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "crypto/rand" - "fmt" - "testing" -) - -func Test_FunctionName(t *testing.T) { - t.Parallel() - AssertEqual(t, "github.com/gofiber/fiber/v3/utils.Test_UUID", FunctionName(Test_UUID)) - - AssertEqual(t, "github.com/gofiber/fiber/v3/utils.Test_FunctionName.func1", FunctionName(func() {})) - - dummyint := 20 - AssertEqual(t, "int", FunctionName(dummyint)) -} - -func Test_UUID(t *testing.T) { - t.Parallel() - res := UUID() - AssertEqual(t, 36, len(res)) - AssertEqual(t, true, res != "00000000-0000-0000-0000-000000000000") -} - -func Test_UUID_Concurrency(t *testing.T) { - t.Parallel() - iterations := 1000 - var res string - ch := make(chan string, iterations) - results := make(map[string]string) - for i := 0; i < iterations; i++ { - go func() { - ch <- UUID() - }() - } - for i := 0; i < iterations; i++ { - res = <-ch - results[res] = res - } - AssertEqual(t, iterations, len(results)) -} - -func Test_UUIDv4(t *testing.T) { - t.Parallel() - res := UUIDv4() - AssertEqual(t, 36, len(res)) - AssertEqual(t, true, res != "00000000-0000-0000-0000-000000000000") -} - -func Test_UUIDv4_Concurrency(t *testing.T) { - t.Parallel() - iterations := 1000 - var res string - ch := make(chan string, iterations) - results := make(map[string]string) - for i := 0; i < iterations; i++ { - go func() { - ch <- UUIDv4() - }() - } - for i := 0; i < iterations; i++ { - res = <-ch - results[res] = res - } - AssertEqual(t, iterations, len(results)) -} - -// go test -v -run=^$ -bench=Benchmark_UUID -benchmem -count=2 - -func Benchmark_UUID(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = UUID() - } - AssertEqual(b, 36, len(res)) - }) - b.Run("default", func(b *testing.B) { - rnd := make([]byte, 16) - _, _ = rand.Read(rnd) - for n := 0; n < b.N; n++ { - res = fmt.Sprintf("%x-%x-%x-%x-%x", rnd[0:4], rnd[4:6], rnd[6:8], rnd[8:10], rnd[10:]) - } - AssertEqual(b, 36, len(res)) - }) -} - -func Test_ConvertToBytes(t *testing.T) { - t.Parallel() - AssertEqual(t, 0, ConvertToBytes("")) - AssertEqual(t, 42, ConvertToBytes("42")) - AssertEqual(t, 42, ConvertToBytes("42b")) - AssertEqual(t, 42, ConvertToBytes("42B")) - AssertEqual(t, 42, ConvertToBytes("42 b")) - AssertEqual(t, 42, ConvertToBytes("42 B")) - - AssertEqual(t, 42*1000, ConvertToBytes("42k")) - AssertEqual(t, 42*1000, ConvertToBytes("42K")) - AssertEqual(t, 42*1000, ConvertToBytes("42kb")) - AssertEqual(t, 42*1000, ConvertToBytes("42KB")) - AssertEqual(t, 42*1000, ConvertToBytes("42 kb")) - AssertEqual(t, 42*1000, ConvertToBytes("42 KB")) - - AssertEqual(t, 42*1000000, ConvertToBytes("42M")) - AssertEqual(t, int(42.5*1000000), ConvertToBytes("42.5MB")) - AssertEqual(t, 42*1000000000, ConvertToBytes("42G")) - - AssertEqual(t, 0, ConvertToBytes("string")) - AssertEqual(t, 0, ConvertToBytes("MB")) -} - -// go test -v -run=^$ -bench=Benchmark_ConvertToBytes -benchmem -count=2 -func Benchmark_ConvertToBytes(b *testing.B) { - var res int - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ConvertToBytes("42B") - } - AssertEqual(b, 42, res) - }) -} diff --git a/utils/convert.go b/utils/convert.go deleted file mode 100644 index 9b456f1ef6..0000000000 --- a/utils/convert.go +++ /dev/null @@ -1,135 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "time" - "unsafe" -) - -// #nosec G103 -// UnsafeString returns a string pointer without allocation -func UnsafeString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} - -// #nosec G103 -// UnsafeBytes returns a byte pointer without allocation -func UnsafeBytes(s string) (bs []byte) { - sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) - bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) - bh.Data = sh.Data - bh.Len = sh.Len - bh.Cap = sh.Len - return -} - -// CopyString copies a string to make it immutable -func CopyString(s string) string { - return string(UnsafeBytes(s)) -} - -// CopyBytes copies a slice to make it immutable -func CopyBytes(b []byte) []byte { - tmp := make([]byte, len(b)) - copy(tmp, b) - return tmp -} - -const ( - uByte = 1 << (10 * iota) - uKilobyte - uMegabyte - uGigabyte - uTerabyte - uPetabyte - uExabyte -) - -// ByteSize returns a human-readable byte string of the form 10M, 12.5K, and so forth. -// The unit that results in the smallest number greater than or equal to 1 is always chosen. -func ByteSize(bytes uint64) string { - unit := "" - value := float64(bytes) - switch { - case bytes >= uExabyte: - unit = "EB" - value /= uExabyte - case bytes >= uPetabyte: - unit = "PB" - value /= uPetabyte - case bytes >= uTerabyte: - unit = "TB" - value /= uTerabyte - case bytes >= uGigabyte: - unit = "GB" - value /= uGigabyte - case bytes >= uMegabyte: - unit = "MB" - value /= uMegabyte - case bytes >= uKilobyte: - unit = "KB" - value /= uKilobyte - case bytes >= uByte: - unit = "B" - default: - return "0B" - } - result := strconv.FormatFloat(value, 'f', 1, 64) - result = strings.TrimSuffix(result, ".0") - return result + unit -} - -// ToString Change arg to string -func ToString(arg any, timeFormat ...string) string { - var tmp = reflect.Indirect(reflect.ValueOf(arg)).Interface() - switch v := tmp.(type) { - case int: - return strconv.Itoa(v) - case int8: - return strconv.FormatInt(int64(v), 10) - case int16: - return strconv.FormatInt(int64(v), 10) - case int32: - return strconv.FormatInt(int64(v), 10) - case int64: - return strconv.FormatInt(v, 10) - case uint: - return strconv.Itoa(int(v)) - case uint8: - return strconv.FormatInt(int64(v), 10) - case uint16: - return strconv.FormatInt(int64(v), 10) - case uint32: - return strconv.FormatInt(int64(v), 10) - case uint64: - return strconv.FormatInt(int64(v), 10) - case string: - return v - case []byte: - return string(v) - case bool: - return strconv.FormatBool(v) - case float32: - return strconv.FormatFloat(float64(v), 'f', -1, 32) - case float64: - return strconv.FormatFloat(v, 'f', -1, 64) - case time.Time: - if len(timeFormat) > 0 { - return v.Format(timeFormat[0]) - } - return v.Format("2006-01-02 15:04:05") - case reflect.Value: - return ToString(v.Interface(), timeFormat...) - case fmt.Stringer: - return v.String() - default: - return "" - } -} diff --git a/utils/convert_test.go b/utils/convert_test.go deleted file mode 100644 index 59ce625e53..0000000000 --- a/utils/convert_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import "testing" - -func Test_UnsafeString(t *testing.T) { - t.Parallel() - res := UnsafeString([]byte("Hello, World!")) - AssertEqual(t, "Hello, World!", res) -} - -// go test -v -run=^$ -bench=UnsafeString -benchmem -count=2 - -func Benchmark_UnsafeString(b *testing.B) { - hello := []byte("Hello, World!") - var res string - b.Run("unsafe", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = UnsafeString(hello) - } - AssertEqual(b, "Hello, World!", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = string(hello) - } - AssertEqual(b, "Hello, World!", res) - }) -} - -func Test_UnsafeBytes(t *testing.T) { - t.Parallel() - res := UnsafeBytes("Hello, World!") - AssertEqual(t, []byte("Hello, World!"), res) -} - -// go test -v -run=^$ -bench=UnsafeBytes -benchmem -count=4 - -func Benchmark_UnsafeBytes(b *testing.B) { - hello := "Hello, World!" - var res []byte - b.Run("unsafe", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = UnsafeBytes(hello) - } - AssertEqual(b, []byte("Hello, World!"), res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = []byte(hello) - } - AssertEqual(b, []byte("Hello, World!"), res) - }) -} - -func Test_CopyString(t *testing.T) { - t.Parallel() - res := CopyString("Hello, World!") - AssertEqual(t, "Hello, World!", res) -} - -func Test_ToString(t *testing.T) { - t.Parallel() - res := ToString([]byte("Hello, World!")) - AssertEqual(t, "Hello, World!", res) - res = ToString(true) - AssertEqual(t, "true", res) - res = ToString(uint(100)) - AssertEqual(t, "100", res) -} - -// go test -v -run=^$ -bench=ToString -benchmem -count=2 -func Benchmark_ToString(b *testing.B) { - hello := []byte("Hello, World!") - for n := 0; n < b.N; n++ { - ToString(hello) - } -} diff --git a/utils/http.go b/utils/http.go deleted file mode 100644 index 61eda1172a..0000000000 --- a/utils/http.go +++ /dev/null @@ -1,242 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import "strings" - -const MIMEOctetStream = "application/octet-stream" - -// GetMIME returns the content-type of a file extension -func GetMIME(extension string) (mime string) { - if len(extension) == 0 { - return mime - } - if extension[0] == '.' { - mime = mimeExtensions[extension[1:]] - } else { - mime = mimeExtensions[extension] - } - if len(mime) == 0 { - return MIMEOctetStream - } - return mime -} - -// ParseVendorSpecificContentType check if content type is vendor specific and -// if it is parsable to any known types. If its not vendor specific then returns -// the original content type. -func ParseVendorSpecificContentType(cType string) string { - plusIndex := strings.Index(cType, "+") - - if plusIndex == -1 { - return cType - } - - var parsableType string - if semiColonIndex := strings.Index(cType, ";"); semiColonIndex == -1 { - parsableType = cType[plusIndex+1:] - } else if plusIndex < semiColonIndex { - parsableType = cType[plusIndex+1 : semiColonIndex] - } else { - return cType[:semiColonIndex] - } - - slashIndex := strings.Index(cType, "/") - - if slashIndex == -1 { - return cType - } - - return cType[0:slashIndex+1] + parsableType -} - -// limits for HTTP statuscodes -const ( - statusMessageMin = 100 - statusMessageMax = 511 -) - -// StatusMessage returns the correct message for the provided HTTP statuscode -func StatusMessage(status int) string { - if status < statusMessageMin || status > statusMessageMax { - return "" - } - return statusMessage[status] -} - -// HTTP status codes were copied from net/http. -var statusMessage = []string{ - 100: "Continue", - 101: "Switching Protocols", - 102: "Processing", - 103: "Early Hints", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 208: "Already Reported", - 226: "IM Used", - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 306: "Switch Proxy", - 307: "Temporary Redirect", - 308: "Permanent Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 418: "I'm a teapot", - 421: "Misdirected Request", - 422: "Unprocessable Entity", - 423: "Locked", - 424: "Failed Dependency", - 426: "Upgrade Required", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates", - 507: "Insufficient Storage", - 508: "Loop Detected", - 510: "Not Extended", - 511: "Network Authentication Required", -} - -// MIME types were copied from https://github.com/nginx/nginx/blob/master/conf/mime.types -var mimeExtensions = map[string]string{ - "html": "text/html", - "htm": "text/html", - "shtml": "text/html", - "css": "text/css", - "gif": "image/gif", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "xml": "application/xml", - "js": "application/javascript", - "atom": "application/atom+xml", - "rss": "application/rss+xml", - "mml": "text/mathml", - "txt": "text/plain", - "jad": "text/vnd.sun.j2me.app-descriptor", - "wml": "text/vnd.wap.wml", - "htc": "text/x-component", - "png": "image/png", - "svg": "image/svg+xml", - "svgz": "image/svg+xml", - "tif": "image/tiff", - "tiff": "image/tiff", - "wbmp": "image/vnd.wap.wbmp", - "webp": "image/webp", - "ico": "image/x-icon", - "jng": "image/x-jng", - "bmp": "image/x-ms-bmp", - "woff": "font/woff", - "woff2": "font/woff2", - "jar": "application/java-archive", - "war": "application/java-archive", - "ear": "application/java-archive", - "json": "application/json", - "hqx": "application/mac-binhex40", - "doc": "application/msword", - "pdf": "application/pdf", - "ps": "application/postscript", - "eps": "application/postscript", - "ai": "application/postscript", - "rtf": "application/rtf", - "m3u8": "application/vnd.apple.mpegurl", - "kml": "application/vnd.google-earth.kml+xml", - "kmz": "application/vnd.google-earth.kmz", - "xls": "application/vnd.ms-excel", - "eot": "application/vnd.ms-fontobject", - "ppt": "application/vnd.ms-powerpoint", - "odg": "application/vnd.oasis.opendocument.graphics", - "odp": "application/vnd.oasis.opendocument.presentation", - "ods": "application/vnd.oasis.opendocument.spreadsheet", - "odt": "application/vnd.oasis.opendocument.text", - "wmlc": "application/vnd.wap.wmlc", - "7z": "application/x-7z-compressed", - "cco": "application/x-cocoa", - "jardiff": "application/x-java-archive-diff", - "jnlp": "application/x-java-jnlp-file", - "run": "application/x-makeself", - "pl": "application/x-perl", - "pm": "application/x-perl", - "prc": "application/x-pilot", - "pdb": "application/x-pilot", - "rar": "application/x-rar-compressed", - "rpm": "application/x-redhat-package-manager", - "sea": "application/x-sea", - "swf": "application/x-shockwave-flash", - "sit": "application/x-stuffit", - "tcl": "application/x-tcl", - "tk": "application/x-tcl", - "der": "application/x-x509-ca-cert", - "pem": "application/x-x509-ca-cert", - "crt": "application/x-x509-ca-cert", - "xpi": "application/x-xpinstall", - "xhtml": "application/xhtml+xml", - "xspf": "application/xspf+xml", - "zip": "application/zip", - "bin": "application/octet-stream", - "exe": "application/octet-stream", - "dll": "application/octet-stream", - "deb": "application/octet-stream", - "dmg": "application/octet-stream", - "iso": "application/octet-stream", - "img": "application/octet-stream", - "msi": "application/octet-stream", - "msp": "application/octet-stream", - "msm": "application/octet-stream", - "mid": "audio/midi", - "midi": "audio/midi", - "kar": "audio/midi", - "mp3": "audio/mpeg", - "ogg": "audio/ogg", - "m4a": "audio/x-m4a", - "ra": "audio/x-realaudio", - "3gpp": "video/3gpp", - "3gp": "video/3gpp", - "ts": "video/mp2t", - "mp4": "video/mp4", - "mpeg": "video/mpeg", - "mpg": "video/mpeg", - "mov": "video/quicktime", - "webm": "video/webm", - "flv": "video/x-flv", - "m4v": "video/x-m4v", - "mng": "video/x-mng", - "asx": "video/x-ms-asf", - "asf": "video/x-ms-asf", - "wmv": "video/x-ms-wmv", - "avi": "video/x-msvideo", -} diff --git a/utils/http_test.go b/utils/http_test.go deleted file mode 100644 index 2abe6c51a4..0000000000 --- a/utils/http_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "mime" - "net/http" - "testing" -) - -func Test_GetMIME(t *testing.T) { - t.Parallel() - res := GetMIME(".json") - AssertEqual(t, "application/json", res) - - res = GetMIME(".xml") - AssertEqual(t, "application/xml", res) - - res = GetMIME("xml") - AssertEqual(t, "application/xml", res) - - res = GetMIME("unknown") - AssertEqual(t, MIMEOctetStream, res) - // empty case - res = GetMIME("") - AssertEqual(t, "", res) -} - -// go test -v -run=^$ -bench=Benchmark_GetMIME -benchmem -count=2 -func Benchmark_GetMIME(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = GetMIME(".xml") - res = GetMIME(".txt") - res = GetMIME(".png") - res = GetMIME(".exe") - res = GetMIME(".json") - } - AssertEqual(b, "application/json", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = mime.TypeByExtension(".xml") - res = mime.TypeByExtension(".txt") - res = mime.TypeByExtension(".png") - res = mime.TypeByExtension(".exe") - res = mime.TypeByExtension(".json") - } - AssertEqual(b, "application/json", res) - }) -} - -func Test_ParseVendorSpecificContentType(t *testing.T) { - t.Parallel() - - cType := ParseVendorSpecificContentType("application/json") - AssertEqual(t, "application/json", cType) - - cType = ParseVendorSpecificContentType("multipart/form-data; boundary=dart-http-boundary-ZnVy.ICWq+7HOdsHqWxCFa8g3D.KAhy+Y0sYJ_lBADypu8po3_X") - AssertEqual(t, "multipart/form-data", cType) - - cType = ParseVendorSpecificContentType("multipart/form-data") - AssertEqual(t, "multipart/form-data", cType) - - cType = ParseVendorSpecificContentType("application/vnd.api+json; version=1") - AssertEqual(t, "application/json", cType) - - cType = ParseVendorSpecificContentType("application/vnd.api+json") - AssertEqual(t, "application/json", cType) - - cType = ParseVendorSpecificContentType("application/vnd.dummy+x-www-form-urlencoded") - AssertEqual(t, "application/x-www-form-urlencoded", cType) - - cType = ParseVendorSpecificContentType("something invalid") - AssertEqual(t, "something invalid", cType) -} - -func Benchmark_ParseVendorSpecificContentType(b *testing.B) { - var cType string - b.Run("vendorContentType", func(b *testing.B) { - for n := 0; n < b.N; n++ { - cType = ParseVendorSpecificContentType("application/vnd.api+json; version=1") - } - AssertEqual(b, "application/json", cType) - }) - - b.Run("defaultContentType", func(b *testing.B) { - for n := 0; n < b.N; n++ { - cType = ParseVendorSpecificContentType("application/json") - } - AssertEqual(b, "application/json", cType) - }) -} - -func Test_StatusMessage(t *testing.T) { - t.Parallel() - res := StatusMessage(204) - AssertEqual(t, "No Content", res) - - res = StatusMessage(404) - AssertEqual(t, "Not Found", res) - - res = StatusMessage(426) - AssertEqual(t, "Upgrade Required", res) - - res = StatusMessage(511) - AssertEqual(t, "Network Authentication Required", res) - - res = StatusMessage(1337) - AssertEqual(t, "", res) - - res = StatusMessage(-1) - AssertEqual(t, "", res) - - res = StatusMessage(0) - AssertEqual(t, "", res) - - res = StatusMessage(600) - AssertEqual(t, "", res) -} - -// go test -run=^$ -bench=Benchmark_StatusMessage -benchmem -count=2 -func Benchmark_StatusMessage(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = StatusMessage(http.StatusNotExtended) - } - AssertEqual(b, "Not Extended", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = http.StatusText(http.StatusNotExtended) - } - AssertEqual(b, "Not Extended", res) - }) -} diff --git a/utils/json.go b/utils/json.go deleted file mode 100644 index 361a7596b8..0000000000 --- a/utils/json.go +++ /dev/null @@ -1,9 +0,0 @@ -package utils - -// JSONMarshal returns the JSON encoding of v. -type JSONMarshal func(v any) ([]byte, error) - -// JSONUnmarshal parses the JSON-encoded data and stores the result -// in the value pointed to by v. If v is nil or not a pointer, -// Unmarshal returns an InvalidUnmarshalError. -type JSONUnmarshal func(data []byte, v any) error diff --git a/utils/json_test.go b/utils/json_test.go deleted file mode 100644 index 1a79e489c0..0000000000 --- a/utils/json_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -import ( - "encoding/json" - "testing" -) - -type sampleStructure struct { - ImportantString string `json:"important_string"` -} - -func Test_GolangJSONEncoder(t *testing.T) { - t.Parallel() - - var ( - ss = &sampleStructure{ - ImportantString: "Hello World", - } - importantString = `{"important_string":"Hello World"}` - jsonEncoder JSONMarshal = json.Marshal - ) - - raw, err := jsonEncoder(ss) - AssertEqual(t, err, nil) - - AssertEqual(t, string(raw), importantString) -} - -func Test_DefaultJSONEncoder(t *testing.T) { - t.Parallel() - - var ( - ss = &sampleStructure{ - ImportantString: "Hello World", - } - importantString = `{"important_string":"Hello World"}` - jsonEncoder JSONMarshal = json.Marshal - ) - - raw, err := jsonEncoder(ss) - AssertEqual(t, err, nil) - - AssertEqual(t, string(raw), importantString) -} - -func Test_DefaultJSONDecoder(t *testing.T) { - t.Parallel() - - var ( - ss sampleStructure - importantString = []byte(`{"important_string":"Hello World"}`) - jsonDecoder JSONUnmarshal = json.Unmarshal - ) - - err := jsonDecoder(importantString, &ss) - AssertEqual(t, err, nil) - AssertEqual(t, "Hello World", ss.ImportantString) -} diff --git a/utils/strings.go b/utils/strings.go deleted file mode 100644 index 109d132f1e..0000000000 --- a/utils/strings.go +++ /dev/null @@ -1,75 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -// ToLower converts ascii string to lower-case -func ToLower(b string) string { - res := make([]byte, len(b)) - copy(res, b) - for i := 0; i < len(res); i++ { - res[i] = toLowerTable[res[i]] - } - - return UnsafeString(res) -} - -// ToUpper converts ascii string to upper-case -func ToUpper(b string) string { - res := make([]byte, len(b)) - copy(res, b) - for i := 0; i < len(res); i++ { - res[i] = toUpperTable[res[i]] - } - - return UnsafeString(res) -} - -// TrimLeft is the equivalent of strings.TrimLeft -func TrimLeft(s string, cutset byte) string { - lenStr, start := len(s), 0 - for start < lenStr && s[start] == cutset { - start++ - } - return s[start:] -} - -// Trim is the equivalent of strings.Trim -func Trim(s string, cutset byte) string { - i, j := 0, len(s)-1 - for ; i <= j; i++ { - if s[i] != cutset { - break - } - } - for ; i < j; j-- { - if s[j] != cutset { - break - } - } - - return s[i : j+1] -} - -// TrimRight is the equivalent of strings.TrimRight -func TrimRight(s string, cutset byte) string { - lenStr := len(s) - for lenStr > 0 && s[lenStr-1] == cutset { - lenStr-- - } - return s[:lenStr] -} - -// EqualFold tests ascii strings for equality case-insensitively -func EqualFold(b, s string) bool { - if len(b) != len(s) { - return false - } - for i := len(b) - 1; i >= 0; i-- { - if toUpperTable[b[i]] != toUpperTable[s[i]] { - return false - } - } - return true -} diff --git a/utils/strings_test.go b/utils/strings_test.go deleted file mode 100644 index c0de5875c6..0000000000 --- a/utils/strings_test.go +++ /dev/null @@ -1,217 +0,0 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io - -package utils - -import ( - "strings" - "testing" -) - -func Test_ToUpper(t *testing.T) { - t.Parallel() - res := ToUpper("/my/name/is/:param/*") - AssertEqual(t, "/MY/NAME/IS/:PARAM/*", res) -} - -const ( - largeStr = "/RePos/GoFiBer/FibEr/iSsues/187643/CoMmEnts/RePos/GoFiBer/FibEr/iSsues/CoMmEnts" - upperStr = "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS/REPOS/GOFIBER/FIBER/ISSUES/COMMENTS" - lowerStr = "/repos/gofiber/fiber/issues/187643/comments/repos/gofiber/fiber/issues/comments" -) - -func Benchmark_ToUpper(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToUpper(largeStr) - } - AssertEqual(b, upperStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToUpper(largeStr) - } - AssertEqual(b, upperStr, res) - }) -} - -func Test_ToLower(t *testing.T) { - t.Parallel() - res := ToLower("/MY/NAME/IS/:PARAM/*") - AssertEqual(t, "/my/name/is/:param/*", res) - res = ToLower("/MY1/NAME/IS/:PARAM/*") - AssertEqual(t, "/my1/name/is/:param/*", res) - res = ToLower("/MY2/NAME/IS/:PARAM/*") - AssertEqual(t, "/my2/name/is/:param/*", res) - res = ToLower("/MY3/NAME/IS/:PARAM/*") - AssertEqual(t, "/my3/name/is/:param/*", res) - res = ToLower("/MY4/NAME/IS/:PARAM/*") - AssertEqual(t, "/my4/name/is/:param/*", res) -} - -func Benchmark_ToLower(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLower(largeStr) - } - AssertEqual(b, lowerStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToLower(largeStr) - } - AssertEqual(b, lowerStr, res) - }) -} - -func Test_TrimRight(t *testing.T) { - t.Parallel() - res := TrimRight("/test//////", '/') - AssertEqual(t, "/test", res) - - res = TrimRight("/test", '/') - AssertEqual(t, "/test", res) - - res = TrimRight(" ", ' ') - AssertEqual(t, "", res) - - res = TrimRight(" ", ' ') - AssertEqual(t, "", res) - - res = TrimRight("", ' ') - AssertEqual(t, "", res) -} - -func Benchmark_TrimRight(b *testing.B) { - var res string - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = TrimRight("foobar ", ' ') - } - AssertEqual(b, "foobar", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.TrimRight("foobar ", " ") - } - AssertEqual(b, "foobar", res) - }) -} - -func Test_TrimLeft(t *testing.T) { - t.Parallel() - res := TrimLeft("////test/", '/') - AssertEqual(t, "test/", res) - - res = TrimLeft("test/", '/') - AssertEqual(t, "test/", res) - - res = TrimLeft(" ", ' ') - AssertEqual(t, "", res) - - res = TrimLeft(" ", ' ') - AssertEqual(t, "", res) - - res = TrimLeft("", ' ') - AssertEqual(t, "", res) -} - -func Benchmark_TrimLeft(b *testing.B) { - var res string - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = TrimLeft(" foobar", ' ') - } - AssertEqual(b, "foobar", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.TrimLeft(" foobar", " ") - } - AssertEqual(b, "foobar", res) - }) -} - -func Test_Trim(t *testing.T) { - t.Parallel() - res := Trim(" test ", ' ') - AssertEqual(t, "test", res) - - res = Trim("test", ' ') - AssertEqual(t, "test", res) - - res = Trim(".test", '.') - AssertEqual(t, "test", res) - - res = Trim(" ", ' ') - AssertEqual(t, "", res) - - res = Trim(" ", ' ') - AssertEqual(t, "", res) - - res = Trim("", ' ') - AssertEqual(t, "", res) -} - -func Benchmark_Trim(b *testing.B) { - var res string - - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = Trim(" foobar ", ' ') - } - AssertEqual(b, "foobar", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.Trim(" foobar ", " ") - } - AssertEqual(b, "foobar", res) - }) - b.Run("default.trimspace", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.TrimSpace(" foobar ") - } - AssertEqual(b, "foobar", res) - }) -} - -// go test -v -run=^$ -bench=Benchmark_EqualFold -benchmem -count=4 -func Benchmark_EqualFold(b *testing.B) { - var res bool - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = EqualFold(upperStr, lowerStr) - } - AssertEqual(b, true, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.EqualFold(upperStr, lowerStr) - } - AssertEqual(b, true, res) - }) -} - -func Test_EqualFold(t *testing.T) { - t.Parallel() - res := EqualFold("/MY/NAME/IS/:PARAM/*", "/my/name/is/:param/*") - AssertEqual(t, true, res) - res = EqualFold("/MY1/NAME/IS/:PARAM/*", "/MY1/NAME/IS/:PARAM/*") - AssertEqual(t, true, res) - res = EqualFold("/my2/name/is/:param/*", "/my2/name") - AssertEqual(t, false, res) - res = EqualFold("/dddddd", "eeeeee") - AssertEqual(t, false, res) - res = EqualFold("\na", "*A") - AssertEqual(t, false, res) - res = EqualFold("/MY3/NAME/IS/:PARAM/*", "/my3/name/is/:param/*") - AssertEqual(t, true, res) - res = EqualFold("/MY4/NAME/IS/:PARAM/*", "/my4/nAME/IS/:param/*") - AssertEqual(t, true, res) -}