diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..6a4ec76843 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org +; This style originates from https://github.com/fewagency/best-practices +root = true + +[*] +charset = utf-8 +end_of_line = lf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..963a68ec2d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto eol=lf + +# Force batch scripts to always use CRLF line endings so that if a repo is accessed +# in Windows via a file share from Linux, the scripts will work. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Force bash scripts to always use LF line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf diff --git a/.github/README.md b/.github/README.md index 45f428f037..1374d8c3f6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -58,6 +58,9 @@ + + +
@@ -69,10 +72,10 @@ - + - + @@ -80,7 +83,7 @@ - +

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. @@ -109,13 +112,13 @@ func main() { These tests are performed by [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) and [Go Web](https://github.com/smallnest/go-web-framework-benchmark). If you want to see all the results, please visit our [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Installation -Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` or higher is required. +Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.16` or higher is required. 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: @@ -148,12 +151,12 @@ 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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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 -Listed below are some of the common examples. If you want to see more code examples , please visit our [Recipes repository](https://github.com/gofiber/recipes) or visit our hosted [API documentation](https://docs.gofiber.io). +Listed below are some of the common examples. If you want to see more code examples, please visit our [Recipes repository](https://github.com/gofiber/recipes) or visit our hosted [API documentation](https://docs.gofiber.io). #### 📖 [**Basic Routing**](https://docs.gofiber.io/#basic-routing) @@ -521,7 +524,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()) diff --git a/.github/README_ckb.md b/.github/README_ckb.md index 92fdba37d6..bab1751537 100644 --- a/.github/README_ckb.md +++ b/.github/README_ckb.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { ئەم تاقیکردنەوانە لەلایەن [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) و [Go Web](https://github.com/smallnest/go-web-framework-benchmark) ئەنجام دراون. دەتوانیت هەموو ئەنجامەکان [لێرە](https://docs.gofiber.io/extra/benchmarks) ببینیت.

- - + +

## ⚙️ دامەزراندن -دڵنیا بە لەوەی کە لەناو ئامێرەکەت Go دامەزراوە ([دای بگرە](https://go.dev/dl/)). دەبێت وەشانەکەشی `1.14` یان سەرووتر بێت. +دڵنیا بە لەوەی کە لەناو ئامێرەکەت Go دامەزراوە ([دای بگرە](https://go.dev/dl/)). دەبێت وەشانەکەشی `1.16` یان سەرووتر بێت. پڕۆژەکەت دەست پێ بکە بە دروستکردنی بوخچەیەک و کار پێ کردنی فەرمانی `go mod init github.com/your/repo` ([زیاتر](https://go.dev/blog/using-go-modules)) لەناو بوخچەکە. دواتریش بەم فەرمانەی خوارەوە فایبەر دامەزرێنە: @@ -520,7 +523,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()) diff --git a/.github/README_de.md b/.github/README_de.md index a2d432622d..09a9043003 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Diese Tests wurden von [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) und [Go Web](https://github.com/smallnest/go-web-framework-benchmark) ausgeführt. Falls du alle Resultate sehen möchtest, besuche bitte unser [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Installation -Stelle sicher, dass du Go installiert hast ([Download hier](https://go.dev/dl/)). Version `1.14` oder neuer wird zu der Nutzung Fibers benötigt. +Stelle sicher, dass du Go installiert hast ([Download hier](https://go.dev/dl/)). Version `1.16` oder neuer wird zu der Nutzung Fibers benötigt. 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: @@ -142,7 +145,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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 @@ -515,7 +518,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()) diff --git a/.github/README_es.md b/.github/README_es.md index fd6bd5ae3c..54b6385f78 100644 --- a/.github/README_es.md +++ b/.github/README_es.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Estas pruebas son realizadas por [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) y [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Si desea ver todos los resultados, visite nuestra [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Instalación -Asegúrese de tener instalado Go ([descargar](https://go.dev/dl/)). Versión `1.14` o superior. +Asegúrese de tener instalado Go ([descargar](https://go.dev/dl/)). Versión `1.16` o superior. 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): @@ -142,7 +145,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.40.0 ha sido probado con las versiones de Go 1.16 a 1.19. +* 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.20. * 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 @@ -515,7 +518,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()) diff --git a/.github/README_fa.md b/.github/README_fa.md index 8c8fad734c..001466ebc0 100644 --- a/.github/README_fa.md +++ b/.github/README_fa.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -120,8 +123,8 @@ func main() {

- - + +


@@ -130,7 +133,7 @@ func main() {

-مطمئن شوید Go را نصب (دانلود) کرده اید. نسخه 1.14 یا بیشتر مورد نیاز است.
+مطمئن شوید Go را نصب (دانلود) کرده اید. نسخه 1.16 یا بیشتر مورد نیاز است.
پروژه خود را با ساختن یک پوشه و سپس اجرای go mod init github.com/your/repo داخل پوشه (یادگیری بیشتر) راه اندازی کنید. سپس Fiber را با دستور go get نصب کنید :

@@ -182,7 +185,7 @@ Fiber از Express الهام گرفته, که محبوب ترین فری

## ⚠️ محدودیت ها -* به دلیل استفاده ناامن از Fiber, ممکن است کتابخانه همیشه با آخرین نسخه Go سازگار نباشد. Fiber 2.40.0 با زبان گو نسخه 1.16 تا 1.19 تست شده است. +* به دلیل استفاده ناامن از Fiber, ممکن است کتابخانه همیشه با آخرین نسخه Go سازگار نباشد. Fiber 2.40.0 با زبان گو نسخه 1.16 تا 1.20 تست شده است. * فریمورک Fiber با پکیج net/http سازگار نیست. این بدان معناست شما نمی توانید از پکیج های مانند go-swagger, gqlgen یا سایر پروژه هایی که بخشی از اکوسیستم net/http هستند استفاده کنید.
@@ -617,7 +620,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()) diff --git a/.github/README_fr.md b/.github/README_fr.md index 11d3db369d..8503c9a297 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Ces tests sont effectués par [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) et [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Si vous voulez voir tous les résultats, n'hésitez pas à consulter notre [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Installation -Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` or higher is required. +Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.16` or higher is required. 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: @@ -142,7 +145,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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 @@ -517,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()) diff --git a/.github/README_he.md b/.github/README_he.md index 622bc0141b..072385e260 100644 --- a/.github/README_he.md +++ b/.github/README_he.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -122,15 +125,15 @@ func main() {

- - + +

## ⚙️ התקנה -Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` or higher is required. +Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.16` or higher is required. 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: @@ -187,7 +190,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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. ## 👀 דוגמאות @@ -617,7 +620,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()) diff --git a/.github/README_id.md b/.github/README_id.md index 84066b1ce8..9b1bbc8d37 100644 --- a/.github/README_id.md +++ b/.github/README_id.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Pengukuran ini dilakukan oleh [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) dan [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Apabila anda ingin melihat hasil lengkapnya, silahkan kunjungi halaman [Wiki](https://docs.gofiber.io/extra/benchmarks) kami.

- - + +

## ⚙️ Instalasi -Pastikan kamu sudah menginstalasi Golang ([unduh](https://go.dev/dl/)). Dengan versi `1.14` atau lebih tinggi [ Direkomendasikan ]. +Pastikan kamu sudah menginstalasi Golang ([unduh](https://go.dev/dl/)). Dengan versi `1.16` atau lebih tinggi [ Direkomendasikan ]. 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): @@ -141,11 +144,11 @@ Bagi yang baru yang beralih dari [Node.js](https://nodejs.org/en/about/) ke [Go] Fiber terinspirasi dari Express, salah satu kerangka kerja web yang paling terkenal di Internet. Kami menggabungkan **kemudahan** dari Express dan **kinerja luar biasa** dari Go. Apabila anda pernah membuat aplikasi dengan Node.js (_dengan Express atau yang lainnya_), maka banyak metode dan prinsip yang akan terasa **sangat umum** bagi anda. -Kami **mendengarkan** para pengguna di [GitHub Issues](https://github.com/gofiber/fiber/issues) (_dan berbagai platform lainnya_) untuk menciptakan kerangka kerja web yang **cepat**, **fleksibel** dan **bersahabat** untuk berbagai macam keperluan, **tenggat waktu** dan **keahlian** para pengguna! Sama halnya seperti yang dilakukan Express di dunia JavaScript. +Kami **mendengarkan** para pengguna di [GitHub Issues](https://github.com/gofiber/fiber/issues), Discord [channel](https://gofiber.io/discord), _dan berbagai platform lainnya_ untuk menciptakan kerangka kerja web yang **cepat**, **fleksibel** dan **bersahabat** untuk berbagai macam keperluan, **tenggat waktu** dan **keahlian** para pengguna! Sama halnya seperti yang dilakukan Express di dunia JavaScript. ## ⚠️ Limitasi -* 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. +* 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.20. * 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 @@ -518,7 +521,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()) diff --git a/.github/README_it.md b/.github/README_it.md index 233e7b216d..d86385b44b 100644 --- a/.github/README_it.md +++ b/.github/README_it.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Questi test sono stati eseguiti da [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) e [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Se vuoi vedere tutti i risultati, visita la nostra [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Installazione -Assicurati di avere Go ([per scaricalro](https://go.dev/dl/)) installato. Devi avere la versione `1.14` o superiore. +Assicurati di avere Go ([per scaricalro](https://go.dev/dl/)) installato. Devi avere la versione `1.16` o superiore. 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): @@ -143,7 +146,7 @@ Fiber è **ispirato** da Express, il web framework più popolare su internet. Ab ## ⚠️ Limitazioni -* 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. +* 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.20 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 @@ -516,7 +519,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()) diff --git a/.github/README_ja.md b/.github/README_ja.md index 007a0aa656..39477937d8 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -106,13 +109,13 @@ func main() { これらのテストは[TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext)および[Go Web](https://github.com/smallnest/go-web-framework-benchmark)によって計測を行っています 。すべての結果を表示するには、 [Wiki](https://docs.gofiber.io/extra/benchmarks)にアクセスしてください。

- - + +

## ⚙️ インストール -Go がインストールされていることを確認してください ([ダウンロード](https://go.dev/dl/)). バージョン `1.14` またはそれ以上であることが必要です。 +Go がインストールされていることを確認してください ([ダウンロード](https://go.dev/dl/)). バージョン `1.16` またはそれ以上であることが必要です。 フォルダを作成し、フォルダ内で `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) コマンドでインストールしてください。 @@ -147,7 +150,7 @@ Fiber は人気の高い Web フレームワークである Expressjs に**イ ## ⚠️ 制限事項 -- Fiber は unsafe パッケージを使用しているため、最新の Go バージョンと互換性がない場合があります。Fiber 2.40.0 は、Go のバージョン 1.16 から 1.19 でテストされています。 +- Fiber は unsafe パッケージを使用しているため、最新の Go バージョンと互換性がない場合があります。Fiber 2.40.0 は、Go のバージョン 1.16 から 1.20 でテストされています。 - Fiber は net/http インターフェースと互換性がありません。つまり、gqlgen や go-swagger など、net/http のエコシステムの一部であるプロジェクトを使用することができません。 ## 👀 例 diff --git a/.github/README_ko.md b/.github/README_ko.md index d1123704f5..f7e6aa966c 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { 이 테스트들은 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext)와 [Go Web](https://github.com/smallnest/go-web-framework-benchmark)을 통해 측정되었습니다. 만약 모든 결과를 보고 싶다면, [Wiki](https://docs.gofiber.io/extra/benchmarks)를 확인해 주세요.

- - + +

## ⚙️ 설치 -Go가 설치되어 있는 것을 확인해 주세요 ([download](https://go.dev/dl/)). 버전 1.14 또는 그 이상이어야 합니다. +Go가 설치되어 있는 것을 확인해 주세요 ([download](https://go.dev/dl/)). 버전 1.16 또는 그 이상이어야 합니다. 폴더를 생성하여 당신의 프로젝트를 초기화하고, 폴더 안에서 `go mod init github.com/your/repo` ([learn more](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를 설치하세요: @@ -144,7 +147,7 @@ Fiber는 인터넷에서 가장 인기있는 웹 프레임워크인 Express에 우리는 **어떤한** 작업, **마감일정**, 개발자의 **기술**이던간에 **빠르고**, **유연하고**, **익숙한** Go 웹 프레임워크를 만들기 위해 사용자들의 [이슈들](https://github.com/gofiber/fiber/issues)을(그리고 모든 인터넷을 통해) **듣고 있습니다**! Express가 자바스크립트 세계에서 하는 것 처럼요. ## ⚠️ 한계점 -* Fiber는 unsafe 패키지를 사용하기 때문에 최신 Go버전과 호환되지 않을 수 있습니다.Fiber 2.40.0은 Go 버전 1.16에서 1.19로 테스트되고 있습니다. +* Fiber는 unsafe 패키지를 사용하기 때문에 최신 Go버전과 호환되지 않을 수 있습니다.Fiber 2.40.0은 Go 버전 1.16에서 1.20로 테스트되고 있습니다. * Fiber는 net/http 인터페이스와 호환되지 않습니다.즉, gqlgen이나 go-swagger 등 net/http 생태계의 일부인 프로젝트를 사용할 수 없습니다. ## 👀 예제 @@ -521,7 +524,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()) diff --git a/.github/README_nl.md b/.github/README_nl.md index 6451506a9f..3c302bfdac 100644 --- a/.github/README_nl.md +++ b/.github/README_nl.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Deze tests zijn uitgevoerd door [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) en [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Bezoek onze [Wiki](https://fiber.wiki/benchmarks) voor alle benchmark resultaten.

- - + +

## ⚙️ Installatie -Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.14` or higher is required. +Make sure you have Go installed ([download](https://go.dev/dl/)). Version `1.16` or higher is required. 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: @@ -144,7 +147,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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 @@ -521,7 +524,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()) diff --git a/.github/README_pt.md b/.github/README_pt.md index 8ebb91ccf2..eb6ca2f3de 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Esses testes são realizados pelo [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) e [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Se você quiser ver todos os resultados, visite nosso [Wiki](https://docs.gofiber.io/extra/benchmarks) .

- - + +

## ⚙️ Instalação -Certifique-se de ter o Go instalado ([download](https://go.dev/dl/)). Versão `1.14` ou superior é obrigatória. +Certifique-se de ter o Go instalado ([download](https://go.dev/dl/)). Versão `1.16` ou superior é obrigatória. 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): @@ -142,7 +145,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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 @@ -515,7 +518,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()) diff --git a/.github/README_ru.md b/.github/README_ru.md index b7dfeebba9..84a639d96b 100644 --- a/.github/README_ru.md +++ b/.github/README_ru.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Тестирование проводилось с помощью [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) и [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Если вы хотите увидеть все результаты, пожалуйста, посетите наш [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ Установка -Убедитесь, что Go установлен ([скачать](https://go.dev/dl/)). Требуется версия `1.14` или выше. +Убедитесь, что Go установлен ([скачать](https://go.dev/dl/)). Требуется версия `1.16` или выше. Инициализируйте проект, создав папку, а затем запустив `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): @@ -144,7 +147,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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. ## 👀 Примеры @@ -521,7 +524,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()) diff --git a/.github/README_sa.md b/.github/README_sa.md index e698bd1ee1..fe77f665fc 100644 --- a/.github/README_sa.md +++ b/.github/README_sa.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -111,13 +114,13 @@ func main() { يتم تنفيذ هذه الاختبارات من قبل [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) و [Go Web](https://github.com/smallnest/go-web-framework-benchmark). إذا كنت تريد رؤية جميع النتائج ، يرجى زيارة موقعنا [Wiki](https://docs.gofiber.io/extra/benchmarks).

- - + +

## ⚙️ تثبيت -تأكد من تثبيت Go ([تحميل](https://go.dev/dl/)). الإصدار `1.14` أو أعلى مطلوب. +تأكد من تثبيت Go ([تحميل](https://go.dev/dl/)). الإصدار `1.16` أو أعلى مطلوب. ابدأ مشروعك بإنشاء مجلد ثم تشغيله `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) أمر: @@ -158,7 +161,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.40.0 has been tested with Go versions 1.16 to 1.19. +* 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.20. * 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. ## 👀 أمثلة @@ -581,7 +584,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()) diff --git a/.github/README_tr.md b/.github/README_tr.md index 4bd5136dfa..fbefed2fb2 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -52,6 +52,9 @@ + + +
@@ -63,10 +66,10 @@ - + - + @@ -105,13 +108,13 @@ func main() { Bu testler [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) ve [Go Web](https://github.com/smallnest/go-web-framework-benchmark) tarafından gerçekleştirildi. Bütün sonuçları görmek için lütfen [Wiki](https://docs.gofiber.io/extra/benchmarks) sayfasını ziyaret ediniz.

- - + +

## ⚙️ Kurulum -Go'nun `1.14` sürümü ([indir](https://go.dev/dl/)) veya daha yüksek bir sürüm gerekli. +Go'nun `1.16` sürümü ([indir](https://go.dev/dl/)) veya daha yüksek bir sürüm gerekli. 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: @@ -143,7 +146,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.40.0, Go 1.14 ile 1.19 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.16 ile 1.20 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 @@ -254,7 +257,7 @@ func main() { fmt.Println("🥇 İlk handler") return c.Next() }) - + // /api ile başlayan bütün routelara etki eder. app.Use("/api", func(c fiber.Ctx) error { fmt.Println("🥈 İkinci handler") @@ -515,7 +518,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()) @@ -600,7 +603,7 @@ Harici olarak barındırılan middlewareların modüllerinin listesi. Bu middlew | [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) | 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. | +| [template](https://github.com/gofiber/template) | Bu paket, Fiber `v2.x.x`, Go sürüm 1.16 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_uk.md b/.github/README_uk.md new file mode 100644 index 0000000000..82fcb23525 --- /dev/null +++ b/.github/README_uk.md @@ -0,0 +1,708 @@ +

+ + Fiber + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ +

+ Fiber — це веб фреймворк, який був натхненний Express + і заснований на Fasthttp, найшвидшому HTTP-двигунові написаному на + Go. Фреймворк розроблено з метою спростити процес швидкої розробки + високопродуктивних веб-додатків з нульовим розподілом пам'яті. +

+ +## ⚡️ Швидкий старт + +```go +package main + +import "github.com/gofiber/fiber/v2" + +func main() { + app := fiber.New() + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Hello, World 👋!") + }) + + app.Listen(":3000") +} +``` + +## 🤖 Еталонні показники + +Тестування проводилося за допомогою [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) +та [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Якщо ви хочете побачити всі результати, будь ласка +відвідайте наш [Wiki](https://docs.gofiber.io/extra/benchmarks). + +

+ + +

+ +## ⚙️ Встановлення + +Переконайтеся, що Go встановлено ([завантажити](https://go.dev/dl/)). Потрібна версія `1.16` або вища. + +Ініціалізуйте проект, створивши папку, а потім запустивши `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 +``` + +## 🎯 Особливості + +- Надійна [маршрутизація](https://docs.gofiber.io/routing) +- Доступ до [статичних файлів](https://docs.gofiber.io/api/app#static) +- Екстремальна [продуктивність](https://docs.gofiber.io/extra/benchmarks) +- [Низький обсяг споживання пам'яті](https://docs.gofiber.io/extra/benchmarks) +- [Кінцеві точки API](https://docs.gofiber.io/api/ctx) +- [Middleware](https://docs.gofiber.io/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) програмування на стороні сервера +- [Двигуни шаблонів](https://github.com/gofiber/template) +- [Підтримка WebSocket](https://github.com/gofiber/websocket) +- [Server-Sent Events](https://github.com/gofiber/recipes/tree/master/sse) +- [Обмежувач швидкості](https://docs.gofiber.io/api/middleware/limiter) +- Документація доступна [18 мовами](https://docs.gofiber.io/) +- І багато іншого, [відвідайте наш Wiki](https://docs.gofiber.io/) + +## 💡 Філософія + +Нові програмісти, які переходять із [Node.js](https://nodejs.org/en/about/) на [Go](https://go.dev/doc/), мають справу зі звивистою кривою навчання, перш ніж можуть розпочати створення своїх веб-додатків або мікросервісів. Fiber, як **веб-фреймворк**, було створено з ідеєю **мінімалізму** та слідує **шляху UNIX**, щоб нові програмісти могли швидко увійти у світ Go з теплим та надійним прийомом. + +Fiber **натхненний** Express, найпопулярнішим веб-фреймворком в Інтернеті. Ми поєднали **легкість** Express і **чисту продуктивність** Go. Якщо ви коли-небудь реалізовували веб-додаток у Node.js (_з використанням Express або подібного_), то багато методів і принципів здадуться вам **дуже звичайними**. + +Ми **прислухаємося** до наших користувачів у [issues](https://github.com/gofiber/fiber/issues), Discord [сервері](https://gofiber.io/discord) та в інших місцях Інтернета, щоб створити **швидкий**, **гнучкий** та **доброзичливий** веб фреймворк на Go для **будь-яких** завдань, **дедлайнів** та **рівнів** розробників! Як це робить Express у світі JavaScript. + +## ⚠️ Обмеження + +- Через те, що Fiber використовує unsafe, бібліотека не завжди може бути сумісною з останньою версією Go. Fiber 2.40.0 було протестовано з Go версій 1.16 до 1.20. +- Fiber не сумісний з інтерфейсами net/http. Це означає, що ви не зможете використовувати такі проекти, як gqlgen, go-swagger або будь-які інші, які є частиною екосистеми net/http. + +## 👀 Приклади + +Нижче наведено деякі типові приклади. Якщо ви хочете переглянути більше прикладів коду, відвідайте наше [репозиторій рецептів](https://github.com/gofiber/recipes) або відвідайте нашу розміщену [документацію API](https://docs.gofiber.io). + +#### 📖 [**Основна маршрутизація**](https://docs.gofiber.io/#basic-routing) + +```go +func main() { + app := fiber.New() + + // GET /api/register + app.Get("/api/*", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("✋ %s", c.Params("*")) + return c.SendString(msg) // => ✋ register + }) + + // GET /flights/LAX-SFO + app.Get("/flights/:from-:to", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("💸 From: %s, To: %s", c.Params("from"), c.Params("to")) + return c.SendString(msg) // => 💸 From: LAX, To: SFO + }) + + // GET /dictionary.txt + app.Get("/:file.:ext", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("📃 %s.%s", c.Params("file"), c.Params("ext")) + return c.SendString(msg) // => 📃 dictionary.txt + }) + + // GET /john/75 + app.Get("/:name/:age/:gender?", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("👴 %s is %s years old", c.Params("name"), c.Params("age")) + return c.SendString(msg) // => 👴 john is 75 years old + }) + + // GET /john + app.Get("/:name", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("Hello, %s 👋!", c.Params("name")) + return c.SendString(msg) // => Hello john 👋! + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +#### 📖 [**Назви маршруту**](https://docs.gofiber.io/api/app#name) + +```go +func main() { + app := fiber.New() + + // GET /api/register + app.Get("/api/*", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("✋ %s", c.Params("*")) + return c.SendString(msg) // => ✋ register + }).Name("api") + + data, _ := json.MarshalIndent(app.GetRoute("api"), "", " ") + fmt.Print(string(data)) + // Prints: + // { + // "method": "GET", + // "name": "api", + // "path": "/api/*", + // "params": [ + // "*1" + // ] + // } + + + log.Fatal(app.Listen(":3000")) +} +``` + +#### 📖 [**Обслуговування статичних файлів**](https://docs.gofiber.io/api/app#static) + +```go +func main() { + app := fiber.New() + + app.Static("/", "./public") + // => http://localhost:3000/js/script.js + // => http://localhost:3000/css/style.css + + app.Static("/prefix", "./public") + // => http://localhost:3000/prefix/js/script.js + // => http://localhost:3000/prefix/css/style.css + + app.Static("*", "./public/index.html") + // => http://localhost:3000/any/path/shows/index/html + + log.Fatal(app.Listen(":3000")) +} +``` + +#### 📖 [**Middleware & Next**](https://docs.gofiber.io/api/ctx#next) + +```go +func main() { + app := fiber.New() + + // Match any route + app.Use(func(c *fiber.Ctx) error { + fmt.Println("🥇 First handler") + return c.Next() + }) + + // Match all routes starting with /api + app.Use("/api", func(c *fiber.Ctx) error { + fmt.Println("🥈 Second handler") + return c.Next() + }) + + // GET /api/list + app.Get("/api/list", func(c *fiber.Ctx) error { + fmt.Println("🥉 Last handler") + return c.SendString("Hello, World 👋!") + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +
+ 📚 Показати більше прикладів коду + +### Двигуни перегляду + +📖 [Конфігурація](https://docs.gofiber.io/api/fiber#config) +📖 [Двигуни](https://github.com/gofiber/template) +📖 [Рендер](https://docs.gofiber.io/api/ctx#render) + +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) або [jade](https://github.com/Joker/jade), тощо. + +Перегляньте наш пакет [Шаблон](https://github.com/gofiber/template), який підтримує кілька двигунів перегляду. + +```go +package main + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/template/pug" +) + +func main() { + // You can setup Views engine before initiation app: + app := fiber.New(fiber.Config{ + Views: pug.New("./views", ".pug"), + }) + + // And now, you can call template `./views/home.pug` like this: + app.Get("/", func(c *fiber.Ctx) error { + return c.Render("home", fiber.Map{ + "title": "Homepage", + "year": 1999, + }) + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +### Групування маршрутів у ланцюги + +📖 [Група](https://docs.gofiber.io/api/app#group) + +```go +func middleware(c *fiber.Ctx) error { + fmt.Println("Don't mind me!") + return c.Next() +} + +func handler(c *fiber.Ctx) error { + return c.SendString(c.Path()) +} + +func main() { + app := fiber.New() + + // Root API route + api := app.Group("/api", middleware) // /api + + // API v1 routes + v1 := api.Group("/v1", middleware) // /api/v1 + v1.Get("/list", handler) // /api/v1/list + v1.Get("/user", handler) // /api/v1/user + + // API v2 routes + v2 := api.Group("/v2", middleware) // /api/v2 + v2.Get("/list", handler) // /api/v2/list + v2.Get("/user", handler) // /api/v2/user + + // ... +} +``` + +### Middleware логування + +📖 [Логування](https://docs.gofiber.io/api/middleware/logger) + +```go +package main + +import ( + "log" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +func main() { + app := fiber.New() + + app.Use(logger.New()) + + // ... + + log.Fatal(app.Listen(":3000")) +} +``` + +### Спільне використання ресурсів між джерелами (CORS) + +📖 [CORS](https://docs.gofiber.io/api/middleware/cors) + +```go +import ( + "log" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" +) + +func main() { + app := fiber.New() + + app.Use(cors.New()) + + // ... + + log.Fatal(app.Listen(":3000")) +} +``` + +Перевірте CORS, передавши будь-який домен у заголовку `Origin`: + +```bash +curl -H "Origin: http://example.com" --verbose http://localhost:3000 +``` + +### Власна відповідь 404 + +📖 [HTTP Методи](https://docs.gofiber.io/api/ctx#status) + +```go +func main() { + app := fiber.New() + + app.Static("/", "./public") + + app.Get("/demo", func(c *fiber.Ctx) error { + return c.SendString("This is a demo!") + }) + + app.Post("/register", func(c *fiber.Ctx) error { + return c.SendString("Welcome!") + }) + + // Last middleware to match anything + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(404) + // => 404 "Not Found" + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +### JSON Відповідь + +📖 [JSON](https://docs.gofiber.io/ctx#json) + +```go +type User struct { + Name string `json:"name"` + Age int `json:"age"` +} + +func main() { + app := fiber.New() + + app.Get("/user", func(c *fiber.Ctx) error { + return c.JSON(&User{"John", 20}) + // => {"name":"John", "age":20} + }) + + app.Get("/json", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{ + "success": true, + "message": "Hi John!", + }) + // => {"success":true, "message":"Hi John!"} + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +### WebSocket Upgrade + +📖 [Websocket](https://github.com/gofiber/websocket) + +```go +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/websocket" +) + +func main() { + app := fiber.New() + + app.Get("/ws", websocket.New(func(c *websocket.Conn) { + for { + mt, msg, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", msg) + err = c.WriteMessage(mt, msg) + if err != nil { + log.Println("write:", err) + break + } + } + })) + + log.Fatal(app.Listen(":3000")) + // ws://localhost:3000/ws +} +``` + +### Server-Sent Events + +📖 [Більше інформації](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/valyala/fasthttp" +) + +func main() { + app := fiber.New() + + app.Get("/sse", func(c *fiber.Ctx) error { + c.Set("Content-Type", "text/event-stream") + c.Set("Cache-Control", "no-cache") + c.Set("Connection", "keep-alive") + c.Set("Transfer-Encoding", "chunked") + + 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()) + fmt.Fprintf(w, "data: Message: %s\n\n", msg) + fmt.Println(msg) + + w.Flush() + time.Sleep(5 * time.Second) + } + })) + + return nil + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +### Recover middleware + +📖 [Recover](https://docs.gofiber.io/api/middleware/recover) + +```go +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/recover" +) + +func main() { + app := fiber.New() + + app.Use(recover.New()) + + app.Get("/", func(c *fiber.Ctx) error { + panic("normally this would crash your app") + }) + + log.Fatal(app.Listen(":3000")) +} +``` + +
+ +### Використання довіреного проксі + +📖 [Конфігурація](https://docs.gofiber.io/api/fiber#config) + +```go +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/recover" +) + +func main() { + app := fiber.New(fiber.Config{ + EnableTrustedProxyCheck: true, + TrustedProxies: []string{"0.0.0.0", "1.1.1.1/30"}, // IP address or IP address range + ProxyHeader: fiber.HeaderXForwardedFor}, + }) + + // ... + + log.Fatal(app.Listen(":3000")) +} +``` + + + +## 🧬 Внутрішні Middleware + +Ось список middleware, яке входить до складу Fiber фреймворку. + +| Middleware | Опис | +|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | Middleware який забезпечує базову автентифікацію по HTTP. | +| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | Middleware який перехоплює та кешує відповіді | +| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | стиснення для Fiber, воно за замовчуванням підтримує `deflate`, `gzip` і `brotli`. | +| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | Middleware який вмикає перехресне використання ресурсів \(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) | Middleware для відкриття змінних середевищ. | +| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | Middleware яке робить кеш-пам’ять більш ефективним і заощаджує пропускну здатність, оскільки веб-серверу не потрібно повторно надсилати повну відповідь, якщо вміст не змінився. | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Middleware який обслуговує доступні варіанти середовища виконання HTTP у форматі JSON. | +| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | Ігнорування значка із журналів або обслуговувати з пам’яті, якщо вказано шлях до файлу. | +| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Middleware файлової системи, особлива подяка та кредити Alireza Salary. | +| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Ообмеження швидкості для Fiber. Використовуйте для обмеження повторних запитів до загальнодоступних API та/або кінцевих точок, таких як скидання пароля. | +| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | Реєстратор запитів/відповідей HTTP. | +| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | Middleware який повідомляє показники сервера. | +| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | Особлива подяка Метью Лі \(@mthli\) . | +| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | Дозволяє надсилати проксі-запити до кількох серверів. | +| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Middleware який відновлює паніки будь-де в ланцюжку стека та передає керування централізованому [обробнику помилок](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) | Middleware для сеансів. ПРИМІТКА: Цей middleware використовує наш пакет зберігання. | +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Middleware який пропускає упакований обробник, якщо предикат є істинним. | +| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | Додає максимальний час для запиту та пересилає до ErrorHandler, якщо його перевищено. | + +## 🧬 Зовнішні Middleware + +Список зовнішніх middleware модулів, які підтримуються [командою Fiber](https://github.com/orgs/gofiber/people). + +| Middleware | Опис | +| :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | +| [adaptor](https://github.com/gofiber/adaptor) | Конвентор для обробників net/http до/з обробників запитів Fiber, особлива подяка @arsmn! | +| [helmet](https://github.com/gofiber/helmet) | Допомагає захистити ваші програми, встановлюючи різні заголовки HTTP. | +| [jwt](https://github.com/gofiber/jwt) | JWT повертає middleware автентифікації JSON Web Token \(JWT\). | +| [keyauth](https://github.com/gofiber/keyauth) | Middleware для автентифікації по ключам. | +| [redirect](https://github.com/gofiber/redirect) | Middleware для перенаправлення. | +| [rewrite](https://github.com/gofiber/rewrite) | Middleware для перезапису URL-адреси на основі наданих правил. | +| [storage](https://github.com/gofiber/storage) | Драйвер зберігання який може використовуватися в різних middleware. | +| [template](https://github.com/gofiber/template) | Цей пакет містить 8 модулів шаблонів, які можна використовувати з Fiber `v1.10.x` Потрібно версія Go 1.13 або новішу. | +| [websocket](https://github.com/gofiber/websocket) | На основі Fasthttp WebSocket для Fiber з підтримкою місцевих користувачів! | + +## 🕶️ Чудовий список + +Більше статей, middleware, прикладів або інструментів дивіться у нашому [чудовому списку](https://github.com/gofiber/awesome-fiber). + +## 👍 Внести свій внесок + +Якщо ви хочете сказати **дякую** та/або підтримати активний розвиток `Fiber`: + +1. Додайте [зірку GitHub](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/) або особистому блогу. +4. Підтримайте проект, пожертвувавши [чашку кави](https://buymeacoff.ee/fenny). + +## ☕ Прихильники + +Fiber – це проект із відкритим вихідним кодом, який працює за рахунок пожертвувань для оплати рахунків, наприклад наше доменне ім’я, gitbook, netlify і безсерверний хостинг. Якщо ви хочете підтримати Fiber, ви можете ☕ [**купити каву тут**](https://buymeacoff.ee/fenny). + +| | Користувач | Пожертвування | +| :--------------------------------------------------------- | :----------------------------------------------- | :------------ | +| ![](https://avatars.githubusercontent.com/u/204341?s=25) | [@destari](https://github.com/destari) | ☕ x 10 | +| ![](https://avatars.githubusercontent.com/u/63164982?s=25) | [@dembygenesis](https://github.com/dembygenesis) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/56607882?s=25) | [@thomasvvugt](https://github.com/thomasvvugt) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/27820675?s=25) | [@hendratommy](https://github.com/hendratommy) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/1094221?s=25) | [@ekaputra07](https://github.com/ekaputra07) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/194590?s=25) | [@jorgefuertes](https://github.com/jorgefuertes) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/186637?s=25) | [@candidosales](https://github.com/candidosales) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/29659953?s=25) | [@l0nax](https://github.com/l0nax) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/635852?s=25) | [@bihe](https://github.com/bihe) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/307334?s=25) | [@justdave](https://github.com/justdave) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/11155743?s=25) | [@koddr](https://github.com/koddr) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/29042462?s=25) | [@lapolinar](https://github.com/lapolinar) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/2978730?s=25) | [@diegowifi](https://github.com/diegowifi) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/44171355?s=25) | [@ssimk0](https://github.com/ssimk0) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/5638101?s=25) | [@raymayemir](https://github.com/raymayemir) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/619996?s=25) | [@melkorm](https://github.com/melkorm) | ☕ x 1 | +| ![](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 Contributors + +## ⭐️ Звіздарі + +Stargazers over time + +## ⚠️ Ліцензія + +Авторське право (c) 2019-дотепер [Fenny](https://github.com/fenny) та [Contributors](https://github.com/gofiber/fiber/graphs/contributors). `Fiber` це безкоштовне програмне забезпечення з відкритим вихідним кодом, ліцензоване згідно [MIT License](https://github.com/gofiber/fiber/blob/master/LICENSE). Офіційний логотип створено [Vic Shóstak](https://github.com/koddr) і поширюється під [Creative Commons](https://creativecommons.org/licenses/by-sa/4.0/) ліцензією (CC BY-SA 4.0 International). + +**Ліцензії сторонніх бібліотек** + +- [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) +- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) +- [runewidth](https://github.com/mattn/go-runewidth/blob/master/LICENSE) +- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) +- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE) +- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE) +- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md) +- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE) +- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE) +- [msgp](https://github.com/tinylib/msgp/blob/master/LICENSE) +- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) +- [uuid](https://github.com/google/uuid/blob/master/LICENSE) +- [wmi](https://github.com/StackExchange/wmi/blob/master/LICENSE) diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md index 4c7fd347e9..b69a521d09 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,9 @@ + + +
@@ -66,10 +69,10 @@ - + - + @@ -107,13 +110,13 @@ func main() { 这些测试由 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) 和 [Go Web](https://github.com/smallnest/go-web-framework-benchmark) 完成。如果您想查看所有结果,请访问我们的 [Wiki](https://docs.gofiber.io/extra/benchmarks) 。

- - + +

## ⚙️ 安装 -确保已安装 `1.14` 或更高版本的 Go ([下载](https://go.dev/dl/))。 +确保已安装 `1.16` 或更高版本的 Go ([下载](https://go.dev/dl/))。 通过创建文件夹并在文件夹内运行 `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: @@ -139,7 +142,7 @@ go get -u github.com/gofiber/fiber/v3 ## 💡 哲学 -从 [Node.js](https://nodejs.org/en/about/) 切换到 [Go](https://go.dev/doc/) 的新 `gopher` 在开始构建 `Web` +从 [Node.js](https://nodejs.org/en/about/) 切换到 [Go](https://go.dev/doc/) 的新 `gopher` 在开始构建 `Web` 应用程序或微服务之前需要经历一段艰难的学习过程。 而 `Fiber`,一个基于**极简主义**并且遵循 **UNIX 方式**创建的 **Web 框架**, 使新的 `gopher` 可以在热烈和可信赖的欢迎中迅速进入 `Go` 的世界。 @@ -149,7 +152,7 @@ go get -u github.com/gofiber/fiber/v3 以及在互联网上的所有诉求,为了创建一个能让有着任何技术栈的开发者都能在 deadline 前完成任务的**迅速**,**灵活**以及**友好**的 `Go web` 框架,就像 `Express` 在 `JavaScript` 世界中一样。 ## ⚠️ 限制 -* 由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.40.0 已经在 Go 1.16 到 1.19 上测试过。 +* 由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.40.0 已经在 Go 1.16 到 1.20 上测试过。 * Fiber 与 net/http 接口不兼容。也就是说你无法直接使用例如 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。 ## 👀 示例 @@ -523,7 +526,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()) diff --git a/.github/README_zh-TW.md b/.github/README_zh-TW.md index 1096fb2f47..0a10c14391 100644 --- a/.github/README_zh-TW.md +++ b/.github/README_zh-TW.md @@ -1,6 +1,6 @@

- Fiber + Fiber
@@ -55,6 +55,12 @@ + + + + + +
@@ -66,10 +72,10 @@ - + - + @@ -77,9 +83,10 @@ +

- Fiber是移植NodeJS的Express框架改以Go語言編寫。本套件基於Fasthttp,Fasthttp有不分配記憶體空間Request Pool的特性,在網路效能方面有著顯著的效能。 + Fiber 是款啟發自 ExpressWeb 框架,建基於 Fasthttp——Go最快的 HTTP 引擎。設計旨在 減輕 快速開發的負擔,兼顧 零記憶體分配效能

## ⚡️ 快速入門 @@ -100,60 +107,59 @@ func main() { } ``` -## 🤖 效能 +## 🤖 效能評定結果 -本測試使用[TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext)和[Go Web 框架效能測試](https://github.com/smallnest/go-web-framework-benchmark)。如果要看全部的執行結果,請到[Wiki](https://docs.gofiber.io/extra/benchmarks) 。 +這些測試由 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) 和 [Go Web 框架效能測試](https://github.com/smallnest/go-web-framework-benchmark) 完成。若需參閱所有結果,請參閱我們的 [Wiki](https://docs.gofiber.io/extra/benchmarks) 資訊。

- - + +

## ⚙️ 安裝 -確保已安裝 Go 版本 `1.14` 或以上 ([下載](https://go.dev/dl/))。 +先確定您已經安裝 `1.16` 或更新版本的 Go([點此下載](https://go.dev/dl/))。 -建立文件夾並在文件夾內執行 `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 : +要初始化專案,首先建立檔案夾,然後在檔案夾中執行 `go mod init github.com/名稱/儲存庫`([深入了解](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/v3 ``` -## 🎯 功能 +## 🎯 特色 -- 強大的[路由](https://docs.gofiber.io/routing) -- [靜態檔案](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) -- 支援中介器和[下一步](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) +- 強固的[路由系統](https://docs.gofiber.io/routing) +- 可以寄存[靜態檔案](https://docs.gofiber.io/api/app#static) +- 疾速[效能](https://docs.gofiber.io/extra/benchmarks) +- 相當低的[記憶體使用量](https://docs.gofiber.io/extra/benchmarks) +- [API 端點](https://docs.gofiber.io/api/ctx) +- 支援 [中介模組](https://docs.gofiber.io/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) 伺服器端服務 - [樣板引擎](https://github.com/gofiber/template) -- 支援[WebSocket](https://github.com/gofiber/websocket) -- [Server-Sent events](https://github.com/gofiber/recipes/tree/master/sse) -- 支援[限速](https://docs.gofiber.io/api/middleware/limiter) -- 被翻譯成[18種語言](https://docs.gofiber.io/) -- 豐富的[文件](https://docs.gofiber.io/) +- [支援 WebSocket](https://github.com/gofiber/websocket) +- [Server-Sent Events](https://github.com/gofiber/recipes/tree/master/sse) +- 支援[速率限制](https://docs.gofiber.io/api/middleware/limiter) +- 有 [18 門語言](https://docs.gofiber.io/)的翻譯 +- 還有很多功能,[開始探索 Fiber](https://docs.gofiber.io/) -## 💡 理念 +## 💡 設計哲學 -不少[Node.js](https://nodejs.org/en/about/)的工程師跳到[Go](https://go.dev/doc/)必須學習一些知識,因此做了一個跟 Express 一樣的 Fiber 省這些麻煩。設計還是照原本的**極簡主義**還有遵循**UNIX 慣例**,因此新手們可以**無痛**迅速進入 Go 的世界。 +從 [Node.js](https://nodejs.org/en/about/) 轉到 [Go](https://go.dev/doc/) 的新進 Go 開發者,得先面對 Go 的各種知識點,才能開始建構自己的 Web 應用程式或微服務。Fiber 作為一款 **Web 框架**,設計之初便以 **極簡主義** 為理念,並遵循 **UNIX 之道**,讓新進 Go 開發者能夠快速隨著友善且值得信賴的社群,進入 Go 的世界。 -Fiber **受到** 網路上最流行的 Web 框架 ExpressJS**啟發**,結合 Express 的**易用性**和 Go 的**高效能**。若你之前用過 Node.js 寫 Web 應用(_使用 ExpressJS/Koa 或類似工具_),那你已經**上手**了。 +Fiber **啟發自** Express——網際網路上最知名的 Web 框架,我們將 Express 的 **易用性** 和 Go 的 **原始效能** 結合在一起。如果您曾經在 Node.js(使用 Express 或類似框架)實作過 Web 應用程式,那麼許多方法和開發準則,將讓您感到 **無比熟悉**。 -有什麼問題請發[issues](https://github.com/gofiber/fiber/issues)或加入 Discord [channel](https://gofiber.io/discord)討論,我們想要創造**快速**、**彈性**、**友善**的社群給**任何人**使用!就像 Express 那樣。 +我們 **傾聽** 使用者在 [Issues](https://github.com/gofiber/fiber/issues)、Discord [群組](https://gofiber.io/discord) 和 **網路上任何角落** 的意見和建議,製作出 **快速**、**靈活** 且 **易於上手** 的 Go Web 框架,來應對**任何**工作、**繳件期限**以及開發者的**能力區間**——如同 Express 在 JavaScript 世界所扮演的角色一樣! -## 限制 -* 由於 Fiber 使用了 unsafe,該庫可能並不總是與最新的 Go 版本兼容。 Fiber 2.40.0 已經用 Go 版本 1.16 到 1.19 進行了測試。 -* Fiber 與 net/http 接口不兼容。 這意味著您將無法使用 gqlgen、go-swagger 或任何其他屬於 net/http 生態系統的項目。 +## ⚠️ 限制 -## 👀 範例 +- 由於 Fiber 有用到 Unsafe,本函式庫有時可能無法相容最新版的 Go 語言。Fiber 2.40.0 已在 Go 1.16 至 1.20 的版本測試過。 +- Fiber 不相容 net/http 的介面,意味著您無法使用像是 gqlgen、go-swagger 或其他任何屬於 net/http 生態系統的專案。 -以下是一些常見範例。 +## 👀 範例 -> 更多程式碼在[範例專案](https://github.com/gofiber/recipes)中或直接看[API 文件](https://docs.gofiber.io)。 +下方列出一些常見範例。如果您想查看更多程式碼範例,請參閱我們的 [Recipes 儲存庫](https://github.com/gofiber/recipes),或前往我們提供的 [API 文件](https://docs.gofiber.io)。 -#### 📖 [**Basic Routing**](https://docs.gofiber.io/#basic-routing) +#### 📖 [**基礎路由**](https://docs.gofiber.io/#basic-routing) ```go func main() { @@ -167,8 +173,8 @@ func main() { // GET /flights/LAX-SFO app.Get("/flights/:from-:to", func(c fiber.Ctx) error { - msg := fmt.Sprintf("💸 From: %s, To: %s", c.Params("from"), c.Params("to")) - return c.SendString(msg) // => 💸 From: LAX, To: SFO + msg := fmt.Sprintf("💸 從:%s,到:%s", c.Params("from"), c.Params("to")) + return c.SendString(msg) // => 💸 從:LAX,到:SFO }) // GET /dictionary.txt @@ -179,14 +185,14 @@ func main() { // GET /john/75 app.Get("/:name/:age/:gender?", func(c fiber.Ctx) error { - msg := fmt.Sprintf("👴 %s is %s years old", c.Params("name"), c.Params("age")) - return c.SendString(msg) // => 👴 john is 75 years old + msg := fmt.Sprintf("👴 %s 已經 %s 歲了", c.Params("name"), c.Params("age")) + return c.SendString(msg) // => 👴 john 已經 75 歲了 }) // GET /john - app.Get("/:name", func(c fiber.Ctx) error { - msg := fmt.Sprintf("Hello, %s 👋!", c.Params("name")) - return c.SendString(msg) // => Hello john 👋! + app.Get("/:name", func(c *fiber.Ctx) error { + msg := fmt.Sprintf("哈囉,%s 👋!", c.Params("name")) + return c.SendString(msg) // => 哈囉,john 👋! }) log.Fatal(app.Listen(":3000")) @@ -194,7 +200,7 @@ func main() { ``` -#### 📖 [**路線命名**](https://docs.gofiber.io/api/app#name) +#### 📖 [**路由命名**](https://docs.gofiber.io/api/app#name) ```go func main() { @@ -208,7 +214,7 @@ func main() { data, _ := json.MarshalIndent(app.GetRoute("api"), "", " ") fmt.Print(string(data)) - // Prints: + // 會輸出: // { // "method": "GET", // "name": "api", @@ -224,7 +230,7 @@ func main() { ``` -#### 📖 [**提供靜態文件**](https://docs.gofiber.io/api/app#static) +#### 📖 [**寄存靜態檔案**](https://docs.gofiber.io/api/app#static) ```go func main() { @@ -246,47 +252,49 @@ func main() { ``` -#### 📖 [**Middleware & Next**](https://docs.gofiber.io/api/ctx#next) +#### 📖 [**中介模組和接續函式 (Next)**](https://docs.gofiber.io/api/ctx#next) ```go func main() { - app := fiber.New() - - // Match any route - app.Use(func(c fiber.Ctx) error { - fmt.Println("🥇 First handler") - return c.Next() - }) - - // Match all routes starting with /api - app.Use("/api", func(c fiber.Ctx) error { - fmt.Println("🥈 Second handler") - return c.Next() - }) - - // GET /api/register - app.Get("/api/list", func(c fiber.Ctx) error { - fmt.Println("🥉 Last handler") - return c.SendString("Hello, World 👋!") - }) - - log.Fatal(app.Listen(":3000")) + app := fiber.New() + + // 符合任何路由 + app.Use(func(c fiber.Ctx) error { + fmt.Println("🥇 第一個處理常式") + return c.Next() + }) + + // 符合所有 /api 開頭的路由 + app.Use("/api", func(c fiber.Ctx) error { + fmt.Println("🥈 第二個處理常式") + return c.Next() + }) + + // GET /api/list + app.Get("/api/list", func(c fiber.Ctx) error { + fmt.Println("🥉 最後一個處理常式") + return c.SendString("Hello, World 👋!") + }) + + log.Fatal(app.Listen(":3000")) } ```
- 📚 顯示更多範例 + 📚 展示更多程式碼範例 -### 界面引擎 +### 檢視引擎 -📖 [設定](https://docs.gofiber.io/api/fiber#config) +📖 [組態設定](https://docs.gofiber.io/api/fiber#config) 📖 [引擎](https://github.com/gofiber/template) -📖 [渲染](https://docs.gofiber.io/api/ctx#render) +📖 [轉譯 (render)](https://docs.gofiber.io/api/ctx#render) -當不指定樣板引擎時 Fiber 預設用[html/template](https://pkg.go.dev/html/template/)。 +若未指定檢視引擎,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)之類…請參考符合多樣板引擎的[樣板](https://github.com/gofiber/template)套件。 +如果您想執行部分檢視 (partials),或者是使用如 [amber](https://github.com/eknkc/amber)、[handlebars](https://github.com/aymerick/raymond)、[mustache](https://github.com/cbroglie/mustache) 或 [pug](https://github.com/Joker/jade) 等等不同的引擎…… + +請參考我們的 [Template](https://github.com/gofiber/template) 套件——它支援多種檢視引擎。 ```go package main @@ -297,12 +305,12 @@ import ( ) func main() { - // You can setup Views engine before initiation app: + // 您可以在 app 初始化前設定檢視 (Views) 引擎: app := fiber.New(fiber.Config{ Views: pug.New("./views", ".pug"), }) - // And now, you can call template `./views/home.pug` like this: + // 現在,您可以用下面這種方式呼叫 `./views/home.pug` 樣板: app.Get("/", func(c fiber.Ctx) error { return c.Render("home", fiber.Map{ "title": "Homepage", @@ -312,16 +320,15 @@ func main() { log.Fatal(app.Listen(":3000")) } - ``` -### 鏈式路線分組 +### 組合路由鏈 -📖 [Group](https://docs.gofiber.io/api/app#group) +📖 [分組](https://docs.gofiber.io/api/app#group) ```go func middleware(c fiber.Ctx) error { - fmt.Println("Don't mind me!") + fmt.Println("不要理我!") return c.Next() } @@ -332,15 +339,15 @@ func handler(c fiber.Ctx) error { func main() { app := fiber.New() - // Root API route + // 根 API 路由 api := app.Group("/api", middleware) // /api - // API v1 routes + // API v1 路由 v1 := api.Group("/v1", middleware) // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user - // API v2 routes + // API v2 路由 v2 := api.Group("/v2", middleware) // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user @@ -350,9 +357,9 @@ func main() { ``` -### 中介器 logger +### 中介模組記錄器 -📖 [Logger](https://docs.gofiber.io/api/middleware/logger) +📖 [記錄器](https://docs.gofiber.io/api/middleware/logger) ```go package main @@ -375,7 +382,7 @@ func main() { } ``` -### 跨網域資源共享 (CORS) +### 跨原始來源資源共用 (CORS) 📖 [CORS](https://docs.gofiber.io/api/middleware/cors) @@ -398,15 +405,15 @@ func main() { } ``` -在`Origin` header 中放網域來檢查 CORS: +在 `Origin` 標頭傳入任何網域來檢查 CORS 的效果: ```bash curl -H "Origin: http://example.com" --verbose http://localhost:3000 ``` -### 客制 404 回應 +### 自訂 404 回應 -📖 [HTTP Methods](https://docs.gofiber.io/api/ctx#status) +📖 [HTTP 方法](https://docs.gofiber.io/api/ctx#status) ```go func main() { @@ -422,7 +429,7 @@ func main() { return c.SendString("Welcome!") }) - // Last middleware to match anything + // 最後一個中介模組,符合所有條件 app.Use(func(c fiber.Ctx) error { return c.SendStatus(404) // => 404 "Not Found" @@ -462,7 +469,7 @@ func main() { } ``` -### WebSocket 升級 +### WebSocket 升級 (Upgrade) 📖 [Websocket](https://github.com/gofiber/websocket) @@ -498,7 +505,7 @@ func main() { ### Server-Sent Events -📖 [More Info](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) +📖 [更多資訊](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) ```go import ( @@ -518,11 +525,11 @@ 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()) - fmt.Fprintf(w, "data: Message: %s\n\n", msg) + msg := fmt.Sprintf("%d - 目前時間為 %v", i, time.Now()) + fmt.Fprintf(w, "data: 訊息: %s\n\n", msg) fmt.Println(msg) w.Flush() @@ -537,125 +544,151 @@ func main() { } ``` -### Recover 中介器 +### Recover 中介模組 📖 [Recover](https://docs.gofiber.io/api/middleware/recover) ```go import ( - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/recover" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { - app := fiber.New() + app := fiber.New() - app.Use(recover.New()) + app.Use(recover.New()) - app.Get("/", func(c fiber.Ctx) error { - panic("normally this would crash your app") - }) + app.Get("/", func(c fiber.Ctx) error { + panic("正常來說,這會導致 app 當機") + }) + + log.Fatal(app.Listen(":3000")) +} +``` - log.Fatal(app.Listen(":3000")) +
+ +### 使用信任的代理伺服器 + +📖 [組態設定](https://docs.gofiber.io/api/fiber#config) + +```go +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/recover" +) + +func main() { + app := fiber.New(fiber.Config{ + EnableTrustedProxyCheck: true, + TrustedProxies: []string{"0.0.0.0", "1.1.1.1/30"}, // IP 地址或 IP 地址區間 + ProxyHeader: fiber.HeaderXForwardedFor}, + }) + + // ... + + log.Fatal(app.Listen(":3000")) } ``` -## 🧬 内部中間件 -以下為包含在Fiber框架中的中間件列表. - -| 中間件 | 描述 | -| :------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | 基本身份驗證中間件提供 HTTP 基本身份驗證。 它為有效憑據調用下一個處理程序,並為丟失或無效憑據調用 401 Unauthorized。 | -| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | 攔截和緩存響應 | -| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | CFiber的壓縮中間件,默認支持`deflate`、`gzip`和`brotli`。 | -| [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) | 如果提供了文件路徑,則忽略日誌中的網站圖標或從內存中提供服務。 | -| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | 用於 Fiber 的 FileSystem 中間件,特別感謝 Alireza Salary | -| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | Fiber 的限速中間件。 用於限制對公共 API 和/或端點的重複請求,例如密碼重置。 | -| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP 請求/響應 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 \(@mthli\) | -| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | 允許您將請求代理到多個服務器 | -| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | 恢復中間件從堆棧鏈中任何地方的恐慌中恢復,並將控制權交給集中式[ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | -| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | 為每個請求添加一個 requestid。 | -| [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) | 添加請求的最大時間,如果超過則轉發給 ErrorHandler。 | - -## 🧬 外部中間件 - -由 [Fiber 團隊] (https://github.com/orgs/gofiber/people) 維護的外部託管中間件模塊列表。 - -| Middleware | Description | -| :------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [adaptor](https://github.com/gofiber/adaptor) | net/http 處理程序與 Fiber 請求處理程序之間的轉換器,特別感謝 @arsmn! | -| [helmet](https://github.com/gofiber/helmet) | 通過設置各種 HTTP 標頭來幫助保護您的應用程序。 | -| [jwt](https://github.com/gofiber/jwt) | JWT 返回一個 JSON Web Token \(JWT\) 身份驗證中間件。 | -| [keyauth](https://github.com/gofiber/keyauth) | Key auth 中間件提供基於密鑰的身份驗證。 | -| [redirect](https://github.com/gofiber/redirect) | Redirect middleware | -| [rewrite](https://github.com/gofiber/rewrite) | 重寫中間件根據提供的規則重寫 URL 路徑。 它有助於向後兼容或只是創建更清晰和更具描述性的鏈接。 | -| [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) |該軟件包包含 8 個模板引擎,可用於 Fiber `v1.10.x` Go 版本 1.13 或更高版本。 | -| [websocket](https://github.com/gofiber/websocket) | 基於 Fasthttp WebSocket for Fiber,支持 Locals! | +## 🧬 內建中介模組 + +這裡列出了 Fiber 框架內建的中介模組。 + +| 中介模組 | 描述 | +| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | 提供 HTTP Basic 認證的基本認證中介模組。如果憑證有效,則會呼叫接續函式 (next);如果沒有憑證或失效,則回傳 401 Unauthorized。 | +| [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) | 啟用跨來源資源共用 (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) | ETag 中介模組,讓快取更高效,同時因為 Web 伺服器不需要在內容未更動時重新傳送完整請求,因此可以減少流量使用。 | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | Expvar 中介模組,透過其 HTTP 伺服器執行階段,提供 JSON 格式的公用變數。 | +| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 不輸出 Favicons 請求記錄;若有提供檔案路徑,則從記憶體提供圖示。 | +| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | 適用於 Fiber 的檔案系統中介模組。特別銘謝 Alireza Salary! | +| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | 適用於 Fiber 的速率限制中介模組。用來限制傳入公開 API 或者(以及)端點(如密碼重設)的重複請求。 | +| [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) | 讓您可以將請求代理 (proxy) 至多台伺服器。 | +| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Recover 中介模組:可以從呼叫堆疊鏈中任何部分的當機 (panic) 中復原,並將控制權交由中央的 [錯誤處理常式 (ErrorHandler)](https://docs.gofiber.io/guide/error-handling) 處理。 | +| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | 為每個請求加上 requestid。 | +| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | 連線階段中介模組。注意:這個中介模組有用到我們的 Storage 套件。 | +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | 略過中介模組,會在條件成立時略過封裝過的處理常式。 | +| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | 為請求加上最長時限,並在逾時後轉送至錯誤處理常式 (ErrorHandler)。 | + +## 🧬 外掛中介模組 + +這裡列出由 [Fiber 團隊](https://github.com/orgs/gofiber/people) 維護、存放在外部的中介模組。 + +| 中介模組 | 描述 | +| :------------------------------------------------ | :----------------------------------------------------------------------------------------------------- | +| [adaptor](https://github.com/gofiber/adaptor) | 將 net/http 處理常式轉換至 Fiber 處理常式,或者是反著做。特別感謝 @arsmn! | +| [helmet](https://github.com/gofiber/helmet) | 透過設定多種 HTTP 標頭,協助保護您應用程式的安全。 | +| [jwt](https://github.com/gofiber/jwt) | JWT 回傳 JSON Web Token \(JWT\) 認證中介模組。 | +| [keyauth](https://github.com/gofiber/keyauth) | Key auth 中介模組提供以金鑰為基礎的認證模式。 | +| [redirect](https://github.com/gofiber/redirect) | 用來重新導向的中介模組。 | +| [rewrite](https://github.com/gofiber/rewrite) | 重寫 (Rewrite) 中介模組:根據提供規則重寫 URL 路徑,適合用來向後相容,或者是製作更乾淨且更好懂的連結。 | +| [storage](https://github.com/gofiber/storage) | 已經做好,實作 Storage 介面的儲存區驅動模組,設計用來與各種 Fiber 中介模組搭配使用。 | +| [template](https://github.com/gofiber/template) | 本套件包含 8 種樣板引擎,可以和 Fiber `v1.10.x` 一起使用。需要 Go 1.13 或更新版本。 | +| [websocket](https://github.com/gofiber/websocket) | 適用於 Fiber,建基於 Fasthttp 的 WebSocket。支援本機空間 (Locals)! | ## 🕶️ 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)。 ## 👍 貢獻 -如果您要說聲**謝謝**或支援`Fiber`的積極發展: +如果您想和我們 **道謝**,或者是支持 `Fiber` 繼續積極開發下去(也可以兩個都做): -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)轉[推](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/)、部落格上發表意見或教學。 -4. 贊助我們[一杯咖啡](https://buymeacoff.ee/fenny)。 +1. 送給專案一顆 [GitHub 星星](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/) 或者是個人部落格上寫下評論或教學。 +4. 捐專案 [一杯咖啡的費用](https://buymeacoff.ee/fenny) 以示支持。 ## ☕ 支持者 -Fiber 是一個以贊助維生的開源專案,像是: 網域、gitbook、netlify、serverless 伺服器。如果你想贊助也可以 ☕ [**買杯咖啡**](https://buymeacoff.ee/fenny) - -| | User | Donation | -| :--------------------------------------------------------- | :----------------------------------------------- | :------- | -| ![](https://avatars.githubusercontent.com/u/204341?s=25) | [@destari](https://github.com/destari) | ☕ x 10 | -| ![](https://avatars.githubusercontent.com/u/63164982?s=25) | [@dembygenesis](https://github.com/dembygenesis) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/56607882?s=25) | [@thomasvvugt](https://github.com/thomasvvugt) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/27820675?s=25) | [@hendratommy](https://github.com/hendratommy) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/1094221?s=25) | [@ekaputra07](https://github.com/ekaputra07) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/194590?s=25) | [@jorgefuertes](https://github.com/jorgefuertes) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/186637?s=25) | [@candidosales](https://github.com/candidosales) | ☕ x 5 | -| ![](https://avatars.githubusercontent.com/u/29659953?s=25) | [@l0nax](https://github.com/l0nax) | ☕ x 3 | -| ![](https://avatars.githubusercontent.com/u/635852?s=25) | [@bihe](https://github.com/bihe) | ☕ x 3 | -| ![](https://avatars.githubusercontent.com/u/307334?s=25) | [@justdave](https://github.com/justdave) | ☕ x 3 | -| ![](https://avatars.githubusercontent.com/u/11155743?s=25) | [@koddr](https://github.com/koddr) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/29042462?s=25) | [@lapolinar](https://github.com/lapolinar) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/2978730?s=25) | [@diegowifi](https://github.com/diegowifi) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/44171355?s=25) | [@ssimk0](https://github.com/ssimk0) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/5638101?s=25) | [@raymayemir](https://github.com/raymayemir) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/619996?s=25) | [@melkorm](https://github.com/melkorm) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/31022056?s=25) | [@marvinjwendt](https://github.com/thomasvvugt) | ☕ x 1 | -| ![](https://avatars.githubusercontent.com/u/31921460?s=25) | [@toishy](https://github.com/toishy) | ☕ x 1 | - -## ‎‍💻 貢獻者 +Fiber 是個仰賴捐款的開放原始碼專案——用來支付如域名、Gitbook、Netlify 和無服務器運算服務的費用。如果您想支持 Fiber,可以在 ☕ [**這裡捐杯咖啡**](https://buymeacoff.ee/fenny)。 + +| | 使用者 | 捐款 | +| :--------------------------------------------------------- | :----------------------------------------------- | :------ | +| ![](https://avatars.githubusercontent.com/u/204341?s=25) | [@destari](https://github.com/destari) | ☕ x 10 | +| ![](https://avatars.githubusercontent.com/u/63164982?s=25) | [@dembygenesis](https://github.com/dembygenesis) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/56607882?s=25) | [@thomasvvugt](https://github.com/thomasvvugt) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/27820675?s=25) | [@hendratommy](https://github.com/hendratommy) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/1094221?s=25) | [@ekaputra07](https://github.com/ekaputra07) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/194590?s=25) | [@jorgefuertes](https://github.com/jorgefuertes) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/186637?s=25) | [@candidosales](https://github.com/candidosales) | ☕ x 5 | +| ![](https://avatars.githubusercontent.com/u/29659953?s=25) | [@l0nax](https://github.com/l0nax) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/635852?s=25) | [@bihe](https://github.com/bihe) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/307334?s=25) | [@justdave](https://github.com/justdave) | ☕ x 3 | +| ![](https://avatars.githubusercontent.com/u/11155743?s=25) | [@koddr](https://github.com/koddr) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/29042462?s=25) | [@lapolinar](https://github.com/lapolinar) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/2978730?s=25) | [@diegowifi](https://github.com/diegowifi) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/44171355?s=25) | [@ssimk0](https://github.com/ssimk0) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/5638101?s=25) | [@raymayemir](https://github.com/raymayemir) | ☕ x 1 | +| ![](https://avatars.githubusercontent.com/u/619996?s=25) | [@melkorm](https://github.com/melkorm) | ☕ x 1 | +| ![](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 Contributors -## ⭐️ Stargazers +## ⭐️ 收藏數 Stargazers over time -## ⚠️ 授權 +## ⚠️ 授權條款 -版權所有 (c) 2019 年至今 [Fenny](https://github.com/fenny) 和 [貢獻者](https://github.com/gofiber/fiber/graphs/contributors)。 `Fiber` 是根據 [MIT 許可證] (https://github.com/gofiber/fiber/blob/master/LICENSE) 許可的免費開源軟件。 官方徽標由 [Vic Shóstak](https://github.com/koddr) 創建並在 [Creative Commons](https://creativecommons.org/licenses/by-sa/4.0/) 許可下分發 (CC BY- SA 4.0 國際)。 +著作權所有 (c) 2019-現在 [Fenny](https://github.com/fenny) 和[貢獻者](https://github.com/gofiber/fiber/graphs/contributors)。`Fiber` 是款依照 [MIT License](https://github.com/gofiber/fiber/blob/master/LICENSE) 授權,免費且開放原始碼的軟體。官方圖示 (logo) 由 [Vic Shóstak](https://github.com/koddr) 製作,並依據 [創用 CC](https://creativecommons.org/licenses/by-sa/4.0/) 授權條款散佈 (CC BY-SA 4.0 International)。 -**Third-party library licenses** +**第三方函式庫的授權條款** - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) - [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) diff --git a/.github/testdata/testRoutes.json b/.github/testdata/testRoutes.json index 8e08aef162..0503d1802e 100644 --- a/.github/testdata/testRoutes.json +++ b/.github/testdata/testRoutes.json @@ -1,6 +1,5 @@ { - "testRoutes": [ - { + "test_routes": [{ "method": "GET", "path": "/authorizations" }, @@ -957,8 +956,7 @@ "path": "/user/keys/1337" } ], - "githubAPI": [ - { + "github_api": [{ "method": "GET", "path": "/authorizations" }, @@ -1915,4 +1913,4 @@ "path": "/user/keys/:id" } ] -} \ No newline at end of file +} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 9cc328eed5..9d1fc3f6a2 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.19.x + go-version: 1.20.x - name: Fetch Repository uses: actions/checkout@v3 - name: Run Benchmark @@ -24,6 +24,7 @@ jobs: tool: 'go' output-file-path: output.txt github-token: ${{ secrets.BENCHMARK_TOKEN }} + benchmark-data-dir-path: 'benchmarks' fail-on-alert: true comment-on-alert: true # TODO: reactivate it later -> when v3 is the stable one diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index e40a67086c..db9a2bb8e5 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,16 +1,29 @@ +# Adapted from https://github.com/golangci/golangci-lint-action/blob/b56f6f529003f1c81d4d759be6bd5f10bf9a0fa0/README.md#how-to-use + +name: golangci-lint on: - push: - branches: - - v3-beta - pull_request: -name: Linter + push: + tags: + - v* + branches: + #- master + #- main + - v3-beta + pull_request: +permissions: + contents: read jobs: - Golint: + golangci: + name: lint runs-on: ubuntu-latest steps: - - name: Fetch Repository - uses: actions/checkout@v3 - - name: Run Golint - uses: reviewdog/action-golangci-lint@v2 + - uses: actions/setup-go@v3 with: - golangci_lint_flags: "--tests=false" + # NOTE: Keep this in sync with the version from go.mod + go-version: 1.20.x + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # NOTE: Keep this in sync with the version from .golangci.yml + version: v1.51.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ffb54108b..99adde703d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: Build: strategy: matrix: - go-version: [1.18.x, 1.19.x] + go-version: [1.19.x, 1.20.x] platform: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml index bddf020f91..06477dccbb 100644 --- a/.github/workflows/vulncheck.yml +++ b/.github/workflows/vulncheck.yml @@ -12,7 +12,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.20.x check-latest: true - name: Fetch Repository uses: actions/checkout@v3 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000000..af3bfb8d72 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,207 @@ +# Created based on v1.51.0 +# NOTE: Keep this in sync with the version in .github/workflows/linter.yml + +run: + modules-download-mode: readonly + skip-dirs-use-default: false + skip-dirs: + - internal + +output: + sort-results: true + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: true + disable-default-exclusions: true + + errchkjson: + report-no-exported: true + + exhaustive: + default-signifies-exhaustive: true + + forbidigo: + forbid: + - ^(fmt\.Print(|f|ln)|print|println)$ + - 'http\.Default(Client|Transport)' + # TODO: Eventually enable these patterns + # - 'time\.Sleep' + # - 'panic' + + gocritic: + disabled-checks: + - ifElseChain + + gofumpt: + module-path: github.com/gofiber/fiber + extra-rules: true + + gosec: + config: + global: + audit: true + + depguard: + include-go-root: true + packages: + - flag + - io/ioutil + packages-with-error-message: + - flag: '`flag` package is only allowed in main.go' + - io/ioutil: '`io/ioutil` package is deprecated, use the `io` and `os` package instead' + + govet: + check-shadowing: true + enable-all: true + disable: + - shadow + - fieldalignment + - loopclosure + + grouper: + import-require-single-import: true + import-require-grouping: true + + misspell: + locale: US + + nolintlint: + require-explanation: true + require-specific: true + + nonamedreturns: + report-error-in-defer: true + + predeclared: + q: true + + promlinter: + strict: true + + revive: + enable-all-rules: true + rules: + # Provided by gomnd linter + - name: add-constant + disabled: true + - name: argument-limit + disabled: true + # Provided by bidichk + - name: banned-characters + disabled: true + - name: cognitive-complexity + disabled: true + - name: cyclomatic + disabled: true + - name: early-return + severity: warning + disabled: true + - name: exported + disabled: true + - name: file-header + disabled: true + - name: function-result-limit + disabled: true + - name: function-length + disabled: true + - name: line-length-limit + disabled: true + - name: max-public-structs + disabled: true + - name: modifies-parameter + disabled: true + - name: nested-structs + disabled: true + - name: package-comments + disabled: true + + stylecheck: + checks: + - all + - -ST1000 + - -ST1020 + - -ST1021 + - -ST1022 + + tagliatelle: + case: + rules: + json: snake + + tenv: + all: true + + #unparam: + # check-exported: true + + wrapcheck: + ignorePackageGlobs: + - github.com/gofiber/fiber/* + - github.com/valyala/fasthttp + +issues: + exclude-use-default: false + +linters: + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - depguard + - dogsled + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - execinquery + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + - goconst + - gocritic + - gofmt + - gofumpt + - goimports + - gomoddirectives + - goprintffuncname + - gosec + - gosimple + - govet + - grouper + - loggercheck + - misspell + - nakedret + - nilerr + - nilnil + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - predeclared + - promlinter + - reassign + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tagliatelle + # - testpackage # TODO: Enable once https://github.com/gofiber/fiber/issues/2252 is implemented + - thelper + # - tparallel # TODO: Enable once https://github.com/gofiber/fiber/issues/2254 is implemented + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + - wrapcheck + - tenv diff --git a/app.go b/app.go index 6afe10af8c..0c328a203e 100644 --- a/app.go +++ b/app.go @@ -9,10 +9,12 @@ package fiber import ( "bufio" + "context" "encoding/json" "encoding/xml" "errors" "fmt" + "log" "net" "net/http" "net/http/httputil" @@ -293,7 +295,7 @@ type Config struct { // FEATURE: v2.3.x // The router executes the same handler by default if StrictRouting or CaseSensitive is disabled. - // Enabling RedirectFixedPath will change this behaviour into a client redirect to the original route path. + // Enabling RedirectFixedPath will change this behavior into a client redirect to the original route path. // Using the status code 301 for GET requests and 308 for all other request methods. // // Default: false @@ -450,7 +452,7 @@ var DefaultMethods = []string{ } // DefaultErrorHandler that process return errors from handlers -var DefaultErrorHandler = func(c Ctx, err error) error { +func DefaultErrorHandler(c Ctx, err error) error { code := StatusInternalServerError var e *Error if errors.As(err, &e) { @@ -562,12 +564,11 @@ func New(config ...Config) *App { func (app *App) handleTrustedProxy(ipAddress string) { if strings.Contains(ipAddress, "/") { _, ipNet, err := net.ParseCIDR(ipAddress) - if err != nil { - fmt.Printf("[Warning] IP range `%s` could not be parsed. \n", ipAddress) + log.Printf("[Warning] IP range %q could not be parsed: %v\n", ipAddress, err) + } else { + app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) } - - app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) } else { app.config.trustedProxiesMap[ipAddress] = struct{}{} } @@ -817,7 +818,7 @@ func (app *App) Config() Config { } // Handler returns the server handler. -func (app *App) Handler() fasthttp.RequestHandler { +func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476 // prepare the server for the start app.startupProcess() return app.handler @@ -834,12 +835,30 @@ func (app *App) HandlersCount() uint32 { } // Shutdown gracefully shuts down the server without interrupting any active connections. -// Shutdown works by first closing all open listeners and then waiting indefinitely for all connections to return to idle and then shut down. +// Shutdown works by first closing all open listeners and then waiting indefinitely for all connections to return to idle before shutting down. // // Make sure the program doesn't exit and waits instead for Shutdown to return. // // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. func (app *App) Shutdown() error { + return app.shutdownWithContext(context.Background()) +} + +// ShutdownWithTimeout gracefully shuts down the server without interrupting any active connections. However, if the timeout is exceeded, +// ShutdownWithTimeout will forcefully close any active connections. +// ShutdownWithTimeout works by first closing all open listeners and then waiting for all connections to return to idle before shutting down. +// +// Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. +// +// ShutdownWithTimeout does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. +func (app *App) ShutdownWithTimeout(timeout time.Duration) error { + ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) + defer cancelFunc() + return app.shutdownWithContext(ctx) +} + +// shutdownWithContext shuts down the server including by force if the context's deadline is exceeded. +func (app *App) shutdownWithContext(ctx context.Context) error { if app.hooks != nil { // TODO: check should be defered? app.hooks.executeOnShutdownHooks() @@ -850,7 +869,7 @@ func (app *App) Shutdown() error { if app.server == nil { return fmt.Errorf("shutdown: server is not running") } - return app.server.Shutdown() + return app.server.ShutdownWithContext(ctx) } // Server returns the underlying fasthttp server @@ -865,7 +884,7 @@ func (app *App) Hooks() *Hooks { // Test is used for internal debugging by passing a *http.Request. // Timeout is optional and defaults to 1s, -1 will disable it completely. -func (app *App) Test(req *http.Request, timeout ...time.Duration) (resp *http.Response, err error) { +func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Response, error) { // Set timeout to := 1 * time.Second if len(timeout) > 0 { @@ -880,15 +899,15 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (resp *http.Re // Dump raw http request dump, err := httputil.DumpRequest(req, true) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to dump request: %w", err) } // Create test connection conn := new(testConn) // Write raw http request - if _, err = conn.r.Write(dump); err != nil { - return nil, err + if _, err := conn.r.Write(dump); err != nil { + return nil, fmt.Errorf("failed to write: %w", err) } // prepare the server for the start app.startupProcess() @@ -921,7 +940,7 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (resp *http.Re } // Check for errors - if err != nil && err != fasthttp.ErrGetOnly { + if err != nil && !errors.Is(err, fasthttp.ErrGetOnly) { return nil, err } @@ -929,12 +948,17 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (resp *http.Re buffer := bufio.NewReader(&conn.w) // Convert raw http response to *http.Response - return http.ReadResponse(buffer, req) + res, err := http.ReadResponse(buffer, req) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + return res, nil } type disableLogger struct{} -func (dl *disableLogger) Printf(_ string, _ ...any) { +func (*disableLogger) Printf(_ string, _ ...any) { // fmt.Println(fmt.Sprintf(format, args...)) } @@ -945,7 +969,7 @@ func (app *App) init() *App { // Only load templates if a view engine is specified if app.config.Views != nil { if err := app.config.Views.Load(); err != nil { - fmt.Printf("views: %v\n", err) + log.Printf("[Warning]: failed to load views: %v\n", err) } } @@ -1020,25 +1044,28 @@ func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { c := app.AcquireCtx().(*DefaultCtx) c.Reset(fctx) - if _, ok := err.(*fasthttp.ErrSmallBuffer); ok { + var errNetOP *net.OpError + + switch { + case errors.As(err, new(*fasthttp.ErrSmallBuffer)): err = ErrRequestHeaderFieldsTooLarge - } else if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() { + case errors.As(err, &errNetOP) && errNetOP.Timeout(): err = ErrRequestTimeout - } else if err == fasthttp.ErrBodyTooLarge { + case errors.Is(err, fasthttp.ErrBodyTooLarge): err = ErrRequestEntityTooLarge - } else if err == fasthttp.ErrGetOnly { + case errors.Is(err, fasthttp.ErrGetOnly): err = ErrMethodNotAllowed - } else if strings.Contains(err.Error(), "timeout") { + case strings.Contains(err.Error(), "timeout"): err = ErrRequestTimeout - } else { - err = ErrBadRequest + default: + err = NewError(StatusBadRequest, err.Error()) } if catch := app.ErrorHandler(c, err); catch != nil { - _ = c.SendStatus(StatusInternalServerError) + log.Printf("serverErrorHandler: failed to call ErrorHandler: %v\n", catch) + _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here + return } - - app.ReleaseCtx(c) } // startupProcess Is the method which executes all the necessary processes just before the start of the server. diff --git a/app_test.go b/app_test.go index 8044bf8e86..853cb84741 100644 --- a/app_test.go +++ b/app_test.go @@ -2,10 +2,12 @@ // 🤖 Github Repository: https://github.com/gofiber/fiber // 📌 API Documentation: https://docs.gofiber.io +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package fiber import ( "bytes" + "context" "crypto/tls" "errors" "fmt" @@ -23,13 +25,14 @@ import ( "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttputil" ) -var testEmptyHandler = func(c Ctx) error { +func testEmptyHandler(_ Ctx) error { return nil } -func testStatus200(t *testing.T, app *App, url string, method string) { +func testStatus200(t *testing.T, app *App, url, method string) { t.Helper() req := httptest.NewRequest(method, url, nil) @@ -40,6 +43,8 @@ func testStatus200(t *testing.T, app *App, url string, method string) { } func testErrorResponse(t *testing.T, err error, resp *http.Response, expectedBodyError string) { + t.Helper() + require.NoError(t, err, "app.Test(req)") require.Equal(t, 500, resp.StatusCode, "Status code") @@ -49,6 +54,7 @@ func testErrorResponse(t *testing.T, err error, resp *http.Response, expectedBod } func Test_App_MethodNotAllowed(t *testing.T) { + t.Parallel() app := New() app.Use(func(c Ctx) error { @@ -100,6 +106,7 @@ func Test_App_MethodNotAllowed(t *testing.T) { } func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T) { + t.Parallel() app := New() app.Use(func(c Ctx) error { @@ -124,6 +131,7 @@ func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T) } func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { + t.Parallel() expectedError := regexp.MustCompile( `error when reading request headers: small read buffer\. Increase ReadBufferSize\. Buffer size=4096, contents: "GET / HTTP/1.1\\r\\nHost: example\.com\\r\\nVery-Long-Header: -+`, ) @@ -137,7 +145,6 @@ func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { logHeaderSlice := make([]string, 5000) request.Header.Set("Very-Long-Header", strings.Join(logHeaderSlice, "-")) _, err := app.Test(request) - if err == nil { t.Error("Expect an error at app.Test(request)") } @@ -146,6 +153,7 @@ func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { } func Test_App_Errors(t *testing.T) { + t.Parallel() app := New(Config{ BodyLimit: 4, }) @@ -169,6 +177,7 @@ func Test_App_Errors(t *testing.T) { } func Test_App_ErrorHandler_Custom(t *testing.T) { + t.Parallel() app := New(Config{ ErrorHandler: func(c Ctx, err error) error { return c.Status(200).SendString("hi, i'm an custom error") @@ -189,6 +198,7 @@ func Test_App_ErrorHandler_Custom(t *testing.T) { } func Test_App_ErrorHandler_HandlerStack(t *testing.T) { + t.Parallel() app := New(Config{ ErrorHandler: func(c Ctx, err error) error { require.Equal(t, "1: USE error", err.Error()) @@ -218,6 +228,7 @@ func Test_App_ErrorHandler_HandlerStack(t *testing.T) { } func Test_App_ErrorHandler_RouteStack(t *testing.T) { + t.Parallel() app := New(Config{ ErrorHandler: func(c Ctx, err error) error { require.Equal(t, "1: USE error", err.Error()) @@ -242,7 +253,19 @@ func Test_App_ErrorHandler_RouteStack(t *testing.T) { require.Equal(t, "1: USE error", string(body)) } +func Test_App_serverErrorHandler_Internal_Error(t *testing.T) { + t.Parallel() + app := New() + msg := "test err" + c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) + + app.serverErrorHandler(c.fasthttp, errors.New(msg)) + require.Equal(t, string(c.fasthttp.Response.Body()), msg) + require.Equal(t, c.fasthttp.Response.StatusCode(), StatusBadRequest) +} + func Test_App_Nested_Params(t *testing.T) { + t.Parallel() app := New() app.Get("/test", func(c Ctx) error { @@ -266,6 +289,7 @@ func Test_App_Nested_Params(t *testing.T) { } func Test_App_Use_Params(t *testing.T) { + t.Parallel() app := New() app.Use("/prefix/:param", func(c Ctx) error { @@ -308,6 +332,7 @@ func Test_App_Use_Params(t *testing.T) { } func Test_App_Use_UnescapedPath(t *testing.T) { + t.Parallel() app := New(Config{UnescapePath: true, CaseSensitive: true}) app.Use("/cRéeR/:param", func(c Ctx) error { @@ -336,6 +361,7 @@ func Test_App_Use_UnescapedPath(t *testing.T) { } func Test_App_Use_CaseSensitive(t *testing.T) { + t.Parallel() app := New(Config{CaseSensitive: true}) app.Use("/abc", func(c Ctx) error { @@ -366,6 +392,7 @@ func Test_App_Use_CaseSensitive(t *testing.T) { } func Test_App_Not_Use_StrictRouting(t *testing.T) { + t.Parallel() app := New() app.Use("/abc", func(c Ctx) error { @@ -399,6 +426,7 @@ func Test_App_Not_Use_StrictRouting(t *testing.T) { } func Test_App_Use_MultiplePrefix(t *testing.T) { + t.Parallel() app := New() app.Use([]string{"/john", "/doe"}, func(c Ctx) error { @@ -441,10 +469,10 @@ func Test_App_Use_MultiplePrefix(t *testing.T) { body, err = io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, "/test/doe", string(body)) - } func Test_App_Use_StrictRouting(t *testing.T) { + t.Parallel() app := New(Config{StrictRouting: true}) app.Get("/abc", func(c Ctx) error { @@ -478,13 +506,14 @@ func Test_App_Use_StrictRouting(t *testing.T) { } func Test_App_Add_Method_Test(t *testing.T) { + t.Parallel() defer func() { if err := recover(); err != nil { require.Equal(t, "add: invalid http method JANE\n", fmt.Sprintf("%v", err)) } }() - methods := append(DefaultMethods, "JOHN") + methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here app := New(Config{ RequestMethods: methods, }) @@ -508,6 +537,7 @@ func Test_App_Add_Method_Test(t *testing.T) { // go test -run Test_App_GETOnly func Test_App_GETOnly(t *testing.T) { + t.Parallel() app := New(Config{ GETOnly: true, }) @@ -523,6 +553,7 @@ func Test_App_GETOnly(t *testing.T) { } func Test_App_Use_Params_Group(t *testing.T) { + t.Parallel() app := New() group := app.Group("/prefix/:param/*") @@ -541,6 +572,7 @@ func Test_App_Use_Params_Group(t *testing.T) { } func Test_App_Chaining(t *testing.T) { + t.Parallel() n := func(c Ctx) error { return c.Next() } @@ -569,6 +601,7 @@ func Test_App_Chaining(t *testing.T) { } func Test_App_Order(t *testing.T) { + t.Parallel() app := New() app.Get("/test", func(c Ctx) error { @@ -598,6 +631,7 @@ func Test_App_Order(t *testing.T) { } func Test_App_Methods(t *testing.T) { + t.Parallel() dummyHandler := testEmptyHandler app := New() @@ -637,6 +671,7 @@ func Test_App_Methods(t *testing.T) { } func Test_App_Route_Naming(t *testing.T) { + t.Parallel() app := New() handler := func(c Ctx) error { return c.SendStatus(StatusOK) @@ -667,6 +702,7 @@ func Test_App_Route_Naming(t *testing.T) { } func Test_App_New(t *testing.T) { + t.Parallel() app := New() app.Get("/", testEmptyHandler) @@ -677,6 +713,7 @@ func Test_App_New(t *testing.T) { } func Test_App_Config(t *testing.T) { + t.Parallel() app := New(Config{ StrictRouting: true, }) @@ -684,12 +721,15 @@ func Test_App_Config(t *testing.T) { } func Test_App_Shutdown(t *testing.T) { + t.Parallel() t.Run("success", func(t *testing.T) { + t.Parallel() app := New() require.True(t, app.Shutdown() == nil) }) t.Run("no server", func(t *testing.T) { + t.Parallel() app := &App{} if err := app.Shutdown(); err != nil { if err.Error() != "shutdown: server is not running" { @@ -699,6 +739,46 @@ func Test_App_Shutdown(t *testing.T) { }) } +func Test_App_ShutdownWithTimeout(t *testing.T) { + t.Parallel() + app := New() + app.Get("/", func(c Ctx) error { + time.Sleep(5 * time.Second) + return c.SendString("body") + }) + ln := fasthttputil.NewInmemoryListener() + go func() { + require.NoError(t, app.Listener(ln)) + }() + time.Sleep(1 * time.Second) + go func() { + conn, err := ln.Dial() + if err != nil { + t.Errorf("unexepcted error: %v", err) + } + + if _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")); err != nil { + t.Errorf("unexpected error: %v", err) + } + }() + time.Sleep(1 * time.Second) + + shutdownErr := make(chan error) + go func() { + shutdownErr <- app.ShutdownWithTimeout(1 * time.Second) + }() + + timer := time.NewTimer(time.Second * 5) + select { + case <-timer.C: + t.Fatal("idle connections not closed on shutdown") + case err := <-shutdownErr: + if err == nil || !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("unexpected err %v. Expecting %v", err, context.DeadlineExceeded) + } + } +} + // go test -run Test_App_Static_Index_Default func Test_App_Static_Index_Default(t *testing.T) { app := New() @@ -753,7 +833,7 @@ func Test_App_Static_Direct(t *testing.T) { body, err = io.ReadAll(resp.Body) require.NoError(t, err) - require.True(t, strings.Contains(string(body), "testRoutes")) + require.True(t, strings.Contains(string(body), "test_routes")) } // go test -run Test_App_Static_MaxAge @@ -762,7 +842,7 @@ func Test_App_Static_MaxAge(t *testing.T) { app.Static("/", "./.github", Static{MaxAge: 100}) - resp, err := app.Test(httptest.NewRequest("GET", "/index.html", nil)) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/index.html", nil)) require.NoError(t, err, "app.Test(req)") require.Equal(t, 200, resp.StatusCode, "Status code") require.False(t, resp.Header.Get(HeaderContentLength) == "") @@ -775,17 +855,17 @@ 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") { + if strings.Contains(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)) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/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)) + normal_resp, normal_err := app.Test(httptest.NewRequest(MethodGet, "/config.yml", nil)) require.Equal(t, nil, normal_err, "app.Test(req)") require.Equal(t, "", normal_resp.Header.Get(HeaderCacheControl), "CacheControl Control") } @@ -796,7 +876,7 @@ 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)) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/fiber.png", nil)) require.NoError(t, err, "app.Test(req)") require.Equal(t, 200, resp.StatusCode, "Status code") require.False(t, resp.Header.Get(HeaderContentLength) == "") @@ -966,7 +1046,7 @@ func Test_App_Static_Next(t *testing.T) { }) t.Run("app.Static is skipped: invoking Get handler", func(t *testing.T) { - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(MethodGet, "/", nil) req.Header.Set("X-Custom-Header", "skip") resp, err := app.Test(req) require.NoError(t, err) @@ -980,7 +1060,7 @@ func Test_App_Static_Next(t *testing.T) { }) t.Run("app.Static is not skipped: serving index.html", func(t *testing.T) { - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(MethodGet, "/", nil) req.Header.Set("X-Custom-Header", "don't skip") resp, err := app.Test(req) require.NoError(t, err) @@ -1039,6 +1119,7 @@ func Test_App_Mixed_Routes_WithSameLen(t *testing.T) { } func Test_App_Group_Invalid(t *testing.T) { + t.Parallel() defer func() { if err := recover(); err != nil { require.Equal(t, "use: invalid handler int\n", fmt.Sprintf("%v", err)) @@ -1048,6 +1129,7 @@ func Test_App_Group_Invalid(t *testing.T) { } func Test_App_Group(t *testing.T) { + t.Parallel() dummyHandler := testEmptyHandler app := New() @@ -1108,6 +1190,7 @@ func Test_App_Group(t *testing.T) { } func Test_App_Route(t *testing.T) { + t.Parallel() dummyHandler := testEmptyHandler app := New() @@ -1156,6 +1239,7 @@ func Test_App_Route(t *testing.T) { } func Test_App_Deep_Group(t *testing.T) { + t.Parallel() runThroughCount := 0 dummyHandler := func(c Ctx) error { runThroughCount++ @@ -1176,6 +1260,7 @@ func Test_App_Deep_Group(t *testing.T) { // go test -run Test_App_Next_Method func Test_App_Next_Method(t *testing.T) { + t.Parallel() app := New() app.Use(func(c Ctx) error { @@ -1210,6 +1295,7 @@ func Benchmark_NewError(b *testing.B) { // go test -run Test_NewError func Test_NewError(t *testing.T) { + t.Parallel() e := NewError(StatusForbidden, "permission denied") require.Equal(t, StatusForbidden, e.Code) require.Equal(t, "permission denied", e.Message) @@ -1217,6 +1303,7 @@ func Test_NewError(t *testing.T) { // go test -run Test_Test_Timeout func Test_Test_Timeout(t *testing.T) { + t.Parallel() app := New() app.Get("/", testEmptyHandler) @@ -1242,17 +1329,19 @@ func (errorReader) Read([]byte) (int, error) { // go test -run Test_Test_DumpError func Test_Test_DumpError(t *testing.T) { + t.Parallel() app := New() app.Get("/", testEmptyHandler) resp, err := app.Test(httptest.NewRequest(MethodGet, "/", errorReader(0))) require.True(t, resp == nil) - require.Equal(t, "errorReader", err.Error()) + require.Equal(t, "failed to dump request: errorReader", err.Error()) } // go test -run Test_App_Handler func Test_App_Handler(t *testing.T) { + t.Parallel() h := New().Handler() require.Equal(t, "fasthttp.RequestHandler", reflect.TypeOf(h).String()) } @@ -1261,10 +1350,11 @@ type invalidView struct{} func (invalidView) Load() error { return errors.New("invalid view") } -func (i invalidView) Render(io.Writer, string, any, ...string) error { panic("implement me") } +func (invalidView) Render(io.Writer, string, any, ...string) error { panic("implement me") } // go test -run Test_App_Init_Error_View func Test_App_Init_Error_View(t *testing.T) { + t.Parallel() app := New(Config{Views: invalidView{}}) defer func() { @@ -1272,11 +1362,14 @@ func Test_App_Init_Error_View(t *testing.T) { require.Equal(t, "implement me", fmt.Sprintf("%v", err)) } }() - _ = app.config.Views.Render(nil, "", nil) + + err := app.config.Views.Render(nil, "", nil) + require.NoError(t, err) } // go test -run Test_App_Stack func Test_App_Stack(t *testing.T) { + t.Parallel() app := New() app.Use("/path0", testEmptyHandler) @@ -1300,6 +1393,7 @@ func Test_App_Stack(t *testing.T) { // go test -run Test_App_HandlersCount func Test_App_HandlersCount(t *testing.T) { + t.Parallel() app := New() app.Use("/path0", testEmptyHandler) @@ -1312,6 +1406,7 @@ func Test_App_HandlersCount(t *testing.T) { // go test -run Test_App_ReadTimeout func Test_App_ReadTimeout(t *testing.T) { + t.Parallel() app := New(Config{ ReadTimeout: time.Nanosecond, IdleTimeout: time.Minute, @@ -1350,6 +1445,7 @@ func Test_App_ReadTimeout(t *testing.T) { // go test -run Test_App_BadRequest func Test_App_BadRequest(t *testing.T) { + t.Parallel() app := New() app.Get("/bad-request", func(c Ctx) error { @@ -1383,6 +1479,7 @@ func Test_App_BadRequest(t *testing.T) { // go test -run Test_App_SmallReadBuffer func Test_App_SmallReadBuffer(t *testing.T) { + t.Parallel() app := New(Config{ ReadBufferSize: 1, }) @@ -1393,11 +1490,12 @@ func Test_App_SmallReadBuffer(t *testing.T) { go func() { time.Sleep(500 * time.Millisecond) - resp, err := http.Get("http://127.0.0.1:4006/small-read-buffer") - if resp != nil { - require.Equal(t, 431, resp.StatusCode) - } + req, err := http.NewRequestWithContext(context.Background(), MethodGet, "http://127.0.0.1:4006/small-read-buffer", http.NoBody) + require.NoError(t, err) + var client http.Client + resp, err := client.Do(req) require.NoError(t, err) + require.Equal(t, 431, resp.StatusCode) require.Nil(t, app.Shutdown()) }() @@ -1405,12 +1503,14 @@ func Test_App_SmallReadBuffer(t *testing.T) { } func Test_App_Server(t *testing.T) { + t.Parallel() app := New() require.False(t, app.Server() == nil) } func Test_App_Error_In_Fasthttp_Server(t *testing.T) { + t.Parallel() app := New() app.config.ErrorHandler = func(c Ctx, err error) error { return errors.New("fake error") @@ -1424,28 +1524,30 @@ func Test_App_Error_In_Fasthttp_Server(t *testing.T) { // go test -race -run Test_App_New_Test_Parallel func Test_App_New_Test_Parallel(t *testing.T) { + t.Parallel() t.Run("Test_App_New_Test_Parallel_1", func(t *testing.T) { t.Parallel() app := New(Config{Immutable: true}) - _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest(MethodGet, "/", 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}) - _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) require.Equal(t, nil, err) }) } func Test_App_ReadBodyStream(t *testing.T) { + t.Parallel() app := New(Config{StreamRequestBody: true}) app.Post("/", func(c Ctx) error { // Calling c.Body() automatically reads the entire stream. return c.SendString(fmt.Sprintf("%v %s", c.Request().IsBodyStream(), c.Body())) }) testString := "this is a test" - resp, err := app.Test(httptest.NewRequest("POST", "/", bytes.NewBufferString(testString))) + resp, err := app.Test(httptest.NewRequest(MethodPost, "/", bytes.NewBufferString(testString))) require.NoError(t, err, "app.Test(req)") body, err := io.ReadAll(resp.Body) require.NoError(t, err, "io.ReadAll(resp.Body)") @@ -1453,6 +1555,7 @@ func Test_App_ReadBodyStream(t *testing.T) { } func Test_App_DisablePreParseMultipartForm(t *testing.T) { + t.Parallel() // Must be used with both otherwise there is no point. testString := "this is a test" @@ -1468,12 +1571,12 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { } file, err := mpf.File["test"][0].Open() if err != nil { - return err + return fmt.Errorf("failed to open: %w", err) } buffer := make([]byte, len(testString)) n, err := file.Read(buffer) if err != nil { - return err + return fmt.Errorf("failed to read: %w", err) } if n != len(testString) { return fmt.Errorf("bad read length") @@ -1489,7 +1592,7 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { require.Equal(t, len(testString), n, "writer n") require.Nil(t, w.Close(), "w.Close()") - req := httptest.NewRequest("POST", "/", b) + req := httptest.NewRequest(MethodPost, "/", b) req.Header.Set("Content-Type", w.FormDataContentType()) resp, err := app.Test(req) require.NoError(t, err, "app.Test(req)") @@ -1500,6 +1603,7 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { } func Test_App_Test_no_timeout_infinitely(t *testing.T) { + t.Parallel() var err error c := make(chan int) @@ -1511,7 +1615,7 @@ func Test_App_Test_no_timeout_infinitely(t *testing.T) { return nil }) - req := httptest.NewRequest(http.MethodGet, "/", http.NoBody) + req := httptest.NewRequest(MethodGet, "/", http.NoBody) _, err = app.Test(req, -1) }() @@ -1532,6 +1636,7 @@ func Test_App_Test_no_timeout_infinitely(t *testing.T) { } func Test_App_SetTLSHandler(t *testing.T) { + t.Parallel() tlsHandler := &TLSHandler{clientHelloInfo: &tls.ClientHelloInfo{ ServerName: "example.golang", }} @@ -1546,7 +1651,8 @@ func Test_App_SetTLSHandler(t *testing.T) { } func Test_App_AddCustomRequestMethod(t *testing.T) { - methods := append(DefaultMethods, "TEST") + t.Parallel() + methods := append(DefaultMethods, "TEST") //nolint:gocritic // We want a new slice here app := New(Config{ RequestMethods: methods, }) @@ -1559,6 +1665,7 @@ func Test_App_AddCustomRequestMethod(t *testing.T) { } func TestApp_GetRoutes(t *testing.T) { + t.Parallel() app := New() app.Use(func(c Ctx) error { return c.Next() diff --git a/client.go b/client.go index b217f39b8f..734f677d3c 100644 --- a/client.go +++ b/client.go @@ -8,11 +8,9 @@ import ( "fmt" "io" "mime/multipart" - "net" "os" "path/filepath" "strconv" - "strings" "sync" "time" @@ -188,11 +186,11 @@ func (a *Agent) Parse() error { uri := a.req.URI() - isTLS := false + var isTLS bool scheme := uri.Scheme() - if bytes.Equal(scheme, strHTTPS) { + if bytes.Equal(scheme, []byte(schemeHTTPS)) { isTLS = true - } else if !bytes.Equal(scheme, strHTTP) { + } else if !bytes.Equal(scheme, []byte(schemeHTTP)) { return fmt.Errorf("unsupported protocol %q. http and https are supported", scheme) } @@ -202,7 +200,7 @@ func (a *Agent) Parse() error { } a.HostClient = &fasthttp.HostClient{ - Addr: addMissingPort(string(uri.Host()), isTLS), + Addr: fasthttp.AddMissingPort(string(uri.Host()), isTLS), Name: name, NoDefaultUserAgentHeader: a.NoDefaultUserAgentHeader, IsTLS: isTLS, @@ -211,18 +209,6 @@ func (a *Agent) Parse() error { return nil } -func addMissingPort(addr string, isTLS bool) string { - n := strings.Index(addr, ":") - if n >= 0 { - return addr - } - port := 80 - if isTLS { - port = 443 - } - return net.JoinHostPort(addr, strconv.Itoa(port)) -} - /************************** Header Setting **************************/ // Set sets the given 'key: value' header. @@ -255,7 +241,7 @@ func (a *Agent) SetBytesV(k string, v []byte) *Agent { // SetBytesKV sets the given 'key: value' header. // // Use AddBytesKV for setting multiple header values under the same key. -func (a *Agent) SetBytesKV(k []byte, v []byte) *Agent { +func (a *Agent) SetBytesKV(k, v []byte) *Agent { a.req.Header.SetBytesKV(k, v) return a @@ -295,7 +281,7 @@ func (a *Agent) AddBytesV(k string, v []byte) *Agent { // // Multiple headers with the same key may be added with this function. // Use SetBytesKV for setting a single header for the given key. -func (a *Agent) AddBytesKV(k []byte, v []byte) *Agent { +func (a *Agent) AddBytesKV(k, v []byte) *Agent { a.req.Header.AddBytesKV(k, v) return a @@ -666,10 +652,8 @@ func (a *Agent) Reuse() *Agent { // certificate chain and host name. func (a *Agent) InsecureSkipVerify() *Agent { if a.HostClient.TLSConfig == nil { - /* #nosec G402 */ - a.HostClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 + a.HostClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // We explicitly let the user set insecure mode here } else { - /* #nosec G402 */ a.HostClient.TLSConfig.InsecureSkipVerify = true } @@ -742,14 +726,14 @@ func (a *Agent) RetryIf(retryIf RetryIfFunc) *Agent { // 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) { +func (a *Agent) Bytes() (int, []byte, []error) { defer a.release() return a.bytes() } -func (a *Agent) bytes() (code int, body []byte, errs []error) { +func (a *Agent) bytes() (code int, body []byte, errs []error) { //nolint:nonamedreturns,revive // We want to overwrite the body in a deferred func. TODO: Check if we really need to do this. We eventually want to get rid of all named returns. if errs = append(errs, a.errs...); len(errs) > 0 { - return + return code, body, errs } var ( @@ -774,7 +758,7 @@ func (a *Agent) bytes() (code int, body []byte, errs []error) { code = resp.StatusCode() } - body = append(a.dest, resp.Body()...) + body = append(a.dest, resp.Body()...) //nolint:gocritic // We want to append to the returned slice here if nilResp { ReleaseResponse(resp) @@ -784,25 +768,25 @@ func (a *Agent) bytes() (code int, body []byte, errs []error) { if a.timeout > 0 { if err := a.HostClient.DoTimeout(req, resp, a.timeout); err != nil { errs = append(errs, err) - return + return code, body, errs } } else if a.maxRedirectsCount > 0 && (string(req.Header.Method()) == MethodGet || string(req.Header.Method()) == MethodHead) { if err := a.HostClient.DoRedirects(req, resp, a.maxRedirectsCount); err != nil { errs = append(errs, err) - return + return code, body, errs } } else if err := a.HostClient.Do(req, resp); err != nil { errs = append(errs, err) } - return + return code, body, errs } func printDebugInfo(req *Request, resp *Response, w io.Writer) { msg := fmt.Sprintf("Connected to %s(%s)\r\n\r\n", req.URI().Host(), resp.RemoteAddr()) - _, _ = w.Write(utils.UnsafeBytes(msg)) - _, _ = req.WriteTo(w) - _, _ = resp.WriteTo(w) + _, _ = w.Write(utils.UnsafeBytes(msg)) //nolint:errcheck // This will never fail + _, _ = req.WriteTo(w) //nolint:errcheck // This will never fail + _, _ = resp.WriteTo(w) //nolint:errcheck // This will never fail } // String returns the status code, string body and errors of url. @@ -811,6 +795,7 @@ func printDebugInfo(req *Request, resp *Response, w io.Writer) { func (a *Agent) String() (int, string, []error) { defer a.release() code, body, errs := a.bytes() + // TODO: There might be a data race here on body. Maybe use utils.CopyBytes on it? return code, utils.UnsafeString(body), errs } @@ -819,12 +804,15 @@ func (a *Agent) String() (int, string, []error) { // 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) { +func (a *Agent) Struct(v any) (int, []byte, []error) { defer a.release() - if code, body, errs = a.bytes(); len(errs) > 0 { - return + + code, body, errs := a.bytes() + if len(errs) > 0 { + return code, body, errs } + // TODO: This should only be done once if a.jsonDecoder == nil { a.jsonDecoder = json.Unmarshal } @@ -833,7 +821,7 @@ func (a *Agent) Struct(v any) (code int, body []byte, errs []error) { errs = append(errs, err) } - return + return code, body, errs } func (a *Agent) release() { @@ -891,7 +879,11 @@ func AcquireClient() *Client { if v == nil { return &Client{} } - return v.(*Client) + c, ok := v.(*Client) + if !ok { + panic(fmt.Errorf("failed to type-assert to *Client")) + } + return c } // ReleaseClient returns c acquired via AcquireClient to client pool. @@ -913,7 +905,11 @@ func ReleaseClient(c *Client) { // no longer needed. This allows Agent recycling, reduces GC pressure // and usually improves performance. func AcquireAgent() *Agent { - return agentPool.Get().(*Agent) + a, ok := agentPool.Get().(*Agent) + if !ok { + panic(fmt.Errorf("failed to type-assert to *Agent")) + } + return a } // ReleaseAgent returns a acquired via AcquireAgent to Agent pool. @@ -936,7 +932,11 @@ func AcquireResponse() *Response { if v == nil { return &Response{} } - return v.(*Response) + r, ok := v.(*Response) + if !ok { + panic(fmt.Errorf("failed to type-assert to *Response")) + } + return r } // ReleaseResponse return resp acquired via AcquireResponse to response pool. @@ -959,7 +959,11 @@ func AcquireArgs() *Args { if v == nil { return &Args{} } - return v.(*Args) + a, ok := v.(*Args) + if !ok { + panic(fmt.Errorf("failed to type-assert to *Args")) + } + return a } // ReleaseArgs returns the object acquired via AcquireArgs to the pool. @@ -980,7 +984,11 @@ func AcquireFormFile() *FormFile { if v == nil { return &FormFile{} } - return v.(*FormFile) + ff, ok := v.(*FormFile) + if !ok { + panic(fmt.Errorf("failed to type-assert to *FormFile")) + } + return ff } // ReleaseFormFile returns the object acquired via AcquireFormFile to the pool. @@ -995,9 +1003,7 @@ func ReleaseFormFile(ff *FormFile) { formFilePool.Put(ff) } -var ( - strHTTP = []byte("http") - strHTTPS = []byte("https") +const ( defaultUserAgent = "fiber" ) diff --git a/client_test.go b/client_test.go index 3b95a94da7..38a0a309e5 100644 --- a/client_test.go +++ b/client_test.go @@ -1,3 +1,4 @@ +//nolint:wrapcheck // We must not wrap errors in tests package fiber import ( @@ -289,6 +290,7 @@ func Test_Client_UserAgent(t *testing.T) { }() t.Run("default", func(t *testing.T) { + t.Parallel() for i := 0; i < 5; i++ { a := Get("http://example.com") @@ -303,6 +305,7 @@ func Test_Client_UserAgent(t *testing.T) { }) t.Run("custom", func(t *testing.T) { + t.Parallel() for i := 0; i < 5; i++ { c := AcquireClient() c.UserAgent = "ua" @@ -322,11 +325,14 @@ func Test_Client_UserAgent(t *testing.T) { } func Test_Client_Agent_Set_Or_Add_Headers(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { c.Request().Header.VisitAll(func(key, value []byte) { if k := string(key); k == "K1" || k == "K2" { - _, _ = c.Write(key) - _, _ = c.Write(value) + _, err := c.Write(key) + require.NoError(t, err) + _, err = c.Write(value) + require.NoError(t, err) } }) return nil @@ -347,6 +353,7 @@ func Test_Client_Agent_Set_Or_Add_Headers(t *testing.T) { } func Test_Client_Agent_Connection_Close(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { if c.Request().Header.ConnectionClose() { return c.SendString("close") @@ -362,6 +369,7 @@ func Test_Client_Agent_Connection_Close(t *testing.T) { } func Test_Client_Agent_UserAgent(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Header.UserAgent()) } @@ -375,6 +383,7 @@ func Test_Client_Agent_UserAgent(t *testing.T) { } func Test_Client_Agent_Cookie(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.SendString( c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4")) @@ -392,6 +401,7 @@ func Test_Client_Agent_Cookie(t *testing.T) { } func Test_Client_Agent_Referer(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Header.Referer()) } @@ -405,6 +415,7 @@ func Test_Client_Agent_Referer(t *testing.T) { } func Test_Client_Agent_ContentType(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Header.ContentType()) } @@ -450,6 +461,7 @@ func Test_Client_Agent_Host(t *testing.T) { } func Test_Client_Agent_QueryString(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().URI().QueryString()) } @@ -463,6 +475,7 @@ func Test_Client_Agent_QueryString(t *testing.T) { } func Test_Client_Agent_BasicAuth(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { // Get authorization header auth := c.Get(HeaderAuthorization) @@ -482,6 +495,7 @@ func Test_Client_Agent_BasicAuth(t *testing.T) { } func Test_Client_Agent_BodyString(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Body()) } @@ -494,6 +508,7 @@ func Test_Client_Agent_BodyString(t *testing.T) { } func Test_Client_Agent_Body(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Body()) } @@ -506,6 +521,7 @@ func Test_Client_Agent_Body(t *testing.T) { } func Test_Client_Agent_BodyStream(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.Send(c.Request().Body()) } @@ -576,6 +592,7 @@ func Test_Client_Agent_Dest(t *testing.T) { }() t.Run("small dest", func(t *testing.T) { + t.Parallel() dest := []byte("de") a := Get("http://example.com") @@ -591,6 +608,7 @@ func Test_Client_Agent_Dest(t *testing.T) { }) t.Run("enough dest", func(t *testing.T) { + t.Parallel() dest := []byte("foobar") a := Get("http://example.com") @@ -611,25 +629,26 @@ type readErrorConn struct { net.Conn } -func (r *readErrorConn) Read(p []byte) (int, error) { +func (*readErrorConn) Read(_ []byte) (int, error) { return 0, fmt.Errorf("error") } -func (r *readErrorConn) Write(p []byte) (int, error) { +func (*readErrorConn) Write(p []byte) (int, error) { return len(p), nil } -func (r *readErrorConn) Close() error { +func (*readErrorConn) Close() error { return nil } -func (r *readErrorConn) LocalAddr() net.Addr { +func (*readErrorConn) LocalAddr() net.Addr { return nil } -func (r *readErrorConn) RemoteAddr() net.Addr { +func (*readErrorConn) RemoteAddr() net.Addr { return nil } + func Test_Client_Agent_RetryIf(t *testing.T) { t.Parallel() @@ -671,6 +690,7 @@ func Test_Client_Agent_RetryIf(t *testing.T) { } func Test_Client_Agent_Json(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { require.Equal(t, MIMEApplicationJSON, string(c.Request().Header.ContentType())) @@ -685,6 +705,7 @@ func Test_Client_Agent_Json(t *testing.T) { } func Test_Client_Agent_Json_Error(t *testing.T) { + t.Parallel() a := Get("http://example.com"). JSONEncoder(json.Marshal). JSON(complex(1, 1)) @@ -697,6 +718,7 @@ func Test_Client_Agent_Json_Error(t *testing.T) { } func Test_Client_Agent_XML(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { require.Equal(t, MIMEApplicationXML, string(c.Request().Header.ContentType())) @@ -711,6 +733,7 @@ func Test_Client_Agent_XML(t *testing.T) { } func Test_Client_Agent_XML_Error(t *testing.T) { + t.Parallel() a := Get("http://example.com"). XML(complex(1, 1)) @@ -722,6 +745,7 @@ func Test_Client_Agent_XML_Error(t *testing.T) { } func Test_Client_Agent_Form(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { require.Equal(t, MIMEApplicationForm, string(c.Request().Header.ContentType())) @@ -816,7 +840,10 @@ func Test_Client_Agent_MultipartForm_SendFiles(t *testing.T) { buf := make([]byte, fh1.Size) f, err := fh1.Open() require.NoError(t, err) - defer func() { _ = f.Close() }() + defer func() { + err := f.Close() + require.NoError(t, err) + }() _, err = f.Read(buf) require.NoError(t, err) require.Equal(t, "form file", string(buf)) @@ -868,13 +895,16 @@ func checkFormFile(t *testing.T, fh *multipart.FileHeader, filename string) { basename := filepath.Base(filename) require.Equal(t, fh.Filename, basename) - b1, err := os.ReadFile(filename) + b1, err := os.ReadFile(filename) //nolint:gosec // We're in a test so reading user-provided files by name is fine require.NoError(t, err) b2 := make([]byte, fh.Size) f, err := fh.Open() require.NoError(t, err) - defer func() { _ = f.Close() }() + defer func() { + err := f.Close() + require.NoError(t, err) + }() _, err = f.Read(b2) require.NoError(t, err) require.Equal(t, b1, b2) @@ -913,6 +943,7 @@ func Test_Client_Agent_SendFile_Error(t *testing.T) { } func Test_Client_Debug(t *testing.T) { + t.Parallel() handler := func(c Ctx) error { return c.SendString("debug") } @@ -927,7 +958,7 @@ func Test_Client_Debug(t *testing.T) { str := output.String() - require.True(t, strings.Contains(str, "Connected to example.com(pipe)")) + require.True(t, strings.Contains(str, "Connected to example.com(InmemoryListener)")) 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")) @@ -1006,6 +1037,7 @@ func Test_Client_Agent_InsecureSkipVerify(t *testing.T) { cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") require.NoError(t, err) + //nolint:gosec // We're in a test so using old ciphers is fine serverTLSConf := &tls.Config{ Certificates: []tls.Certificate{cer}, } @@ -1093,6 +1125,7 @@ func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { }() t.Run("success", func(t *testing.T) { + t.Parallel() a := Get("http://example.com?foo"). MaxRedirectsCount(1) @@ -1106,6 +1139,7 @@ func Test_Client_Agent_MaxRedirectsCount(t *testing.T) { }) t.Run("error", func(t *testing.T) { + t.Parallel() a := Get("http://example.com"). MaxRedirectsCount(1) @@ -1174,6 +1208,7 @@ func Test_Client_Agent_Struct(t *testing.T) { }) t.Run("error", func(t *testing.T) { + t.Parallel() a := Get("http://example.com/error") a.HostClient.Dial = func(addr string) (net.Conn, error) { return ln.Dial() } @@ -1189,11 +1224,12 @@ func Test_Client_Agent_Struct(t *testing.T) { }) t.Run("nil jsonDecoder", func(t *testing.T) { + t.Parallel() a := AcquireAgent() defer ReleaseAgent(a) defer a.ConnectionClose() request := a.Request() - request.Header.SetMethod("GET") + request.Header.SetMethod(MethodGet) request.SetRequestURI("http://example.com") err := a.Parse() require.NoError(t, err) @@ -1215,13 +1251,8 @@ func Test_Client_Agent_Parse(t *testing.T) { require.Nil(t, a.Parse()) } -func Test_AddMissingPort_TLS(t *testing.T) { - addr := addMissingPort("example.com", true) - require.Equal(t, "example.com:443", addr) -} - func testAgent(t *testing.T, handler Handler, wrapAgent func(agent *Agent), excepted string, count ...int) { - t.Parallel() + t.Helper() ln := fasthttputil.NewInmemoryListener() @@ -1263,8 +1294,8 @@ type errorMultipartWriter struct { count int } -func (e *errorMultipartWriter) Boundary() string { return "myBoundary" } -func (e *errorMultipartWriter) SetBoundary(_ string) error { return nil } +func (*errorMultipartWriter) Boundary() string { return "myBoundary" } +func (*errorMultipartWriter) SetBoundary(_ string) error { return nil } func (e *errorMultipartWriter) CreateFormFile(_, _ string) (io.Writer, error) { if e.count == 0 { e.count++ @@ -1272,8 +1303,8 @@ func (e *errorMultipartWriter) CreateFormFile(_, _ string) (io.Writer, error) { } return errorWriter{}, nil } -func (e *errorMultipartWriter) WriteField(_, _ string) error { return errors.New("WriteField error") } -func (e *errorMultipartWriter) Close() error { return errors.New("Close error") } +func (*errorMultipartWriter) WriteField(_, _ string) error { return errors.New("WriteField error") } +func (*errorMultipartWriter) Close() error { return errors.New("Close error") } type errorWriter struct{} diff --git a/ctx.go b/ctx.go index e56f47df89..d6b94675a6 100644 --- a/ctx.go +++ b/ctx.go @@ -26,6 +26,11 @@ import ( "github.com/valyala/fasthttp" ) +const ( + schemeHTTP = "http" + schemeHTTPS = "https" +) + // maxParams defines the maximum number of parameters per route. const maxParams = 30 @@ -61,9 +66,10 @@ type TLSHandler struct { } // GetClientInfo Callback function to set CHI +// TODO: Why is this a getter which sets stuff? func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) { t.clientHelloInfo = info - return nil, nil + return nil, nil //nolint:nilnil // Not returning anything useful here is probably fine } // Range data for c.Range @@ -224,8 +230,8 @@ func (c *DefaultCtx) Body() []byte { var body []byte // faster than peek c.Request().Header.VisitAll(func(key, value []byte) { - if utils.UnsafeString(key) == HeaderContentEncoding { - encoding = utils.UnsafeString(value) + if c.app.getString(key) == HeaderContentEncoding { + encoding = c.app.getString(value) } }) @@ -393,6 +399,7 @@ func (c *DefaultCtx) FormFile(key string) (*multipart.FileHeader, error) { } // FormValue returns the first value by key from a MultipartForm. +// Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. // Defaults to the empty string "" if the form value doesn't exist. // If a default value is given, it will return that value if the form value does not exist. // Returned value is only valid within the handler. Do not store any references. @@ -498,8 +505,11 @@ func (c *DefaultCtx) Hostname() string { // Port returns the remote port of the request. func (c *DefaultCtx) Port() string { - port := c.fasthttp.RemoteAddr().(*net.TCPAddr).Port - return strconv.Itoa(port) + tcpaddr, ok := c.fasthttp.RemoteAddr().(*net.TCPAddr) + if !ok { + panic(fmt.Errorf("failed to type-assert to *net.TCPAddr")) + } + return strconv.Itoa(tcpaddr.Port) } // IP returns the remote IP address of the request. @@ -516,13 +526,16 @@ func (c *DefaultCtx) IP() string { // 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 { + // TODO: Reuse the c.extractIPFromHeader func somehow in here + 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 + const maxEstimatedCount = 8 + estimatedCount := len(headerValue) / maxEstimatedCount + if estimatedCount > maxEstimatedCount { + estimatedCount = maxEstimatedCount // Avoid big allocation on big header } ipsFound := make([]string, 0, estimatedCount) @@ -532,8 +545,7 @@ func (c *DefaultCtx) extractIPsFromHeader(header string) []string { iploop: for { - v4 := false - v6 := false + var v4, v6 bool // Manually splitting string without allocating slice, working with parts directly i, j = j+1, j+2 @@ -583,8 +595,9 @@ func (c *DefaultCtx) extractIPFromHeader(header string) string { iploop: for { - v4 := false - v6 := false + var v4, v6 bool + + // Manually splitting string without allocating slice, working with parts directly i, j = j+1, j+2 if j > len(headerValue) { @@ -618,14 +631,14 @@ func (c *DefaultCtx) extractIPFromHeader(header string) string { return c.fasthttp.RemoteIP().String() } - // default behaviour if IP validation is not enabled is just to return whatever value is + // default behavior 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) { +func (c *DefaultCtx) IPs() []string { return c.extractIPsFromHeader(HeaderXForwardedFor) } @@ -664,7 +677,7 @@ func (c *DefaultCtx) JSON(data any) error { func (c *DefaultCtx) JSONP(data any, callback ...string) error { raw, err := json.Marshal(data) if err != nil { - return err + return fmt.Errorf("failed to marshal: %w", err) } var result, cb string @@ -702,11 +715,11 @@ func (c *DefaultCtx) Links(link ...string) { bb := bytebufferpool.Get() for i := range link { if i%2 == 0 { - _ = bb.WriteByte('<') - _, _ = bb.WriteString(link[i]) - _ = bb.WriteByte('>') + _ = bb.WriteByte('<') //nolint:errcheck // This will never fail + _, _ = bb.WriteString(link[i]) //nolint:errcheck // This will never fail + _ = bb.WriteByte('>') //nolint:errcheck // This will never fail } else { - _, _ = bb.WriteString(`; rel="` + link[i] + `",`) + _, _ = bb.WriteString(`; rel="` + link[i] + `",`) //nolint:errcheck // This will never fail } } c.setCanonical(HeaderLink, strings.TrimRight(c.app.getString(bb.Bytes()), ",")) @@ -715,7 +728,7 @@ func (c *DefaultCtx) Links(link ...string) { // 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 any, value ...any) (val any) { +func (c *DefaultCtx) Locals(key any, value ...any) any { if len(value) == 0 { return c.fasthttp.UserValue(key) } @@ -758,9 +771,10 @@ func (c *DefaultCtx) ClientHelloInfo() *tls.ClientHelloInfo { } // Next executes the next method in the stack that matches the current route. -func (c *DefaultCtx) Next() (err error) { +func (c *DefaultCtx) Next() error { // Increment handler index c.indexHandler++ + var err error // Did we executed all route handlers? if c.indexHandler < len(c.route.Handlers) { // Continue route stack @@ -776,7 +790,7 @@ func (c *DefaultCtx) Next() (err error) { return err } -// RestartRouting instead of going to the next handler. This may be usefull after +// RestartRouting instead of going to the next handler. This may be useful after // changing the request path. Note that handlers might be executed again. func (c *DefaultCtx) RestartRouting() error { var err error @@ -832,9 +846,8 @@ func (c *DefaultCtx) ParamsInt(key string, defaultValue ...int) (int, error) { if err != nil { if len(defaultValue) > 0 { return defaultValue[0], nil - } else { - return 0, err } + return 0, fmt.Errorf("failed to convert: %w", err) } return value, nil @@ -859,15 +872,16 @@ func (c *DefaultCtx) Path(override ...string) string { // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Scheme() string { if c.fasthttp.IsTLS() { - return "https" + return schemeHTTPS } if !c.IsProxyTrusted() { - return "http" + return schemeHTTP } - scheme := "http" + scheme := schemeHTTP + const lenXHeaderName = 12 c.fasthttp.Request.Header.VisitAll(func(key, val []byte) { - if len(key) < 12 { + if len(key) < lenXHeaderName { return // Neither "X-Forwarded-" nor "X-Url-Scheme" } switch { @@ -882,7 +896,7 @@ func (c *DefaultCtx) Scheme() string { scheme = v } } else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) { - scheme = "https" + scheme = schemeHTTPS } case bytes.Equal(key, []byte(HeaderXUrlScheme)): @@ -906,25 +920,45 @@ func (c *DefaultCtx) Query(key string, defaultValue ...string) string { return defaultString(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), defaultValue) } +// QueryInt returns integer value of key string parameter in the url. +// Default to empty or invalid key is 0. +// +// GET /?name=alex&wanna_cake=2&id= +// QueryInt("wanna_cake", 1) == 2 +// QueryInt("name", 1) == 1 +// QueryInt("id", 1) == 1 +// QueryInt("id") == 0 +func (c *DefaultCtx) QueryInt(key string, defaultValue ...int) int { + // Use Atoi to convert the param to an int or return zero and an error + value, err := strconv.Atoi(c.app.getString(c.fasthttp.QueryArgs().Peek(key))) + if err != nil { + if len(defaultValue) > 0 { + return defaultValue[0] + } + return 0 + } + + return value +} + // Range returns a struct containing the type and a slice of ranges. -func (c *DefaultCtx) Range(size int) (rangeData Range, err error) { +func (c *DefaultCtx) Range(size int) (Range, error) { + var rangeData Range rangeStr := c.Get(HeaderRange) if rangeStr == "" || !strings.Contains(rangeStr, "=") { - err = ErrRangeMalformed - return + return rangeData, ErrRangeMalformed } data := strings.Split(rangeStr, "=") - if len(data) != 2 { - err = ErrRangeMalformed - return + const expectedDataParts = 2 + if len(data) != expectedDataParts { + return rangeData, ErrRangeMalformed } rangeData.Type = data[0] arr := strings.Split(data[1], ",") for i := 0; i < len(arr); i++ { item := strings.Split(arr[i], "-") if len(item) == 1 { - err = ErrRangeMalformed - return + return rangeData, ErrRangeMalformed } start, startErr := strconv.Atoi(item[0]) end, endErr := strconv.Atoi(item[1]) @@ -949,11 +983,10 @@ func (c *DefaultCtx) Range(size int) (rangeData Range, err error) { }) } if len(rangeData.Ranges) < 1 { - err = ErrRangeUnsatisfiable - return + return rangeData, ErrRangeUnsatisfiable } - return + return rangeData, nil } // Redirect returns the Redirect reference. @@ -987,7 +1020,7 @@ func (c *DefaultCtx) getLocationFromRoute(route Route, params Map) (string, erro if !segment.IsParam { _, err := buf.WriteString(segment.Const) if err != nil { - return "", err + return "", fmt.Errorf("failed to write string: %w", err) } continue } @@ -998,7 +1031,7 @@ func (c *DefaultCtx) getLocationFromRoute(route Route, params Map) (string, erro if isSame || isGreedy { _, err := buf.WriteString(utils.ToString(val)) if err != nil { - return "", err + return "", fmt.Errorf("failed to write string: %w", err) } } } @@ -1017,7 +1050,6 @@ func (c *DefaultCtx) GetRouteURL(routeName string, params Map) (string, error) { // 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 { - var err error // Get new buffer from pool buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -1039,7 +1071,7 @@ func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { // Render template from Views if app.config.Views != nil { if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil { - return err + return fmt.Errorf("failed to render: %w", err) } rendered = true @@ -1051,17 +1083,18 @@ func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { if !rendered { // Render raw template using 'name' as filepath if no engine is set var tmpl *template.Template - if _, err = readContent(buf, name); err != nil { + if _, err := readContent(buf, name); err != nil { return err } // Parse template - if tmpl, err = template.New("").Parse(c.app.getString(buf.Bytes())); err != nil { - return err + tmpl, err := template.New("").Parse(c.app.getString(buf.Bytes())) + if err != nil { + return fmt.Errorf("failed to parse: %w", err) } buf.Reset() // Render template - if err = tmpl.Execute(buf, bind); err != nil { - return err + if err := tmpl.Execute(buf, bind); err != nil { + return fmt.Errorf("failed to execute: %w", err) } } @@ -1069,8 +1102,8 @@ func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8) // Set rendered template to body c.fasthttp.Response.SetBody(buf.Bytes()) - // Return err if exist - return err + + return nil } func (c *DefaultCtx) renderExtensions(bind Map) { @@ -1122,20 +1155,24 @@ func (c *DefaultCtx) SaveFile(fileheader *multipart.FileHeader, path string) err func (c *DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error { file, err := fileheader.Open() if err != nil { - return err + return fmt.Errorf("failed to open: %w", err) } content, err := io.ReadAll(file) if err != nil { - return err + return fmt.Errorf("failed to read: %w", err) + } + + if err := storage.Set(path, content, 0); err != nil { + return fmt.Errorf("failed to store: %w", err) } - return storage.Set(path, content, 0) + return nil } // Secure returns whether a secure connection was established. func (c *DefaultCtx) Secure() bool { - return c.Protocol() == "https" + return c.Protocol() == schemeHTTPS } // Send sets the HTTP response body without copying it. @@ -1161,6 +1198,7 @@ func (c *DefaultCtx) SendFile(file string, compress ...bool) error { // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134 sendFileOnce.Do(func() { + const cacheDuration = 10 * time.Second sendFileFS = &fasthttp.FS{ Root: "", AllowEmptyRoot: true, @@ -1168,7 +1206,7 @@ func (c *DefaultCtx) SendFile(file string, compress ...bool) error { AcceptByteRange: true, Compress: true, CompressedFileSuffix: c.app.config.CompressedFileSuffix, - CacheDuration: 10 * time.Second, + CacheDuration: cacheDuration, IndexNames: []string{"index.html"}, PathNotFound: func(ctx *fasthttp.RequestCtx) { ctx.Response.SetStatusCode(StatusNotFound) @@ -1192,7 +1230,7 @@ func (c *DefaultCtx) SendFile(file string, compress ...bool) error { var err error file = filepath.FromSlash(file) if file, err = filepath.Abs(file); err != nil { - return err + return fmt.Errorf("failed to determine abs file path: %w", err) } if hasTrailingSlash { file += "/" @@ -1257,11 +1295,11 @@ func (c *DefaultCtx) SendStream(stream io.Reader, size ...int) error { } // Set sets the response's HTTP header field to the specified key, value. -func (c *DefaultCtx) Set(key string, val string) { +func (c *DefaultCtx) Set(key, val string) { c.fasthttp.Response.Header.Set(key, val) } -func (c *DefaultCtx) setCanonical(key string, val string) { +func (c *DefaultCtx) setCanonical(key, val string) { c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val)) } @@ -1332,6 +1370,7 @@ func (c *DefaultCtx) Write(p []byte) (int, error) { // Writef appends f & a into response body writer. func (c *DefaultCtx) Writef(f string, a ...any) (int, error) { + //nolint:wrapcheck // This must not be wrapped return fmt.Fprintf(c.fasthttp.Response.BodyWriter(), f, a...) } @@ -1344,7 +1383,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.EqualFold(c.Get(HeaderXRequestedWith), "xmlhttprequest") + return utils.EqualFold(c.app.getBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest")) } // configDependentPaths set paths for route recognition and prepared paths for the user, @@ -1373,8 +1412,9 @@ func (c *DefaultCtx) configDependentPaths() { // Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed, // since the first three characters area select a list of routes c.treePath = c.treePath[0:0] - if len(c.detectionPath) >= 3 { - c.treePath = c.detectionPath[:3] + const maxDetectionPaths = 3 + if len(c.detectionPath) >= maxDetectionPaths { + c.treePath = c.detectionPath[:maxDetectionPaths] } } @@ -1386,13 +1426,14 @@ func (c *DefaultCtx) IsProxyTrusted() bool { return true } - _, trusted := c.app.config.trustedProxiesMap[c.fasthttp.RemoteIP().String()] - if trusted { - return trusted + ip := c.fasthttp.RemoteIP() + + if _, trusted := c.app.config.trustedProxiesMap[ip.String()]; trusted { + return true } for _, ipNet := range c.app.config.trustedProxyRanges { - if ipNet.Contains(c.fasthttp.RemoteIP()) { + if ipNet.Contains(ip) { return true } } diff --git a/ctx_interface.go b/ctx_interface.go index de3d16cbf3..816a3c00b4 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -226,6 +226,16 @@ type Ctx interface { // Make copies or use the Immutable setting to use the value outside the Handler. Query(key string, defaultValue ...string) string + // QueryInt returns integer value of key string parameter in the url. + // Default to empty or invalid key is 0. + // + // GET /?name=alex&wanna_cake=2&id= + // QueryInt("wanna_cake", 1) == 2 + // QueryInt("name", 1) == 1 + // QueryInt("id", 1) == 1 + // QueryInt("id") == 0 + QueryInt(key string, defaultValue ...int) int + // Range returns a struct containing the type and a slice of ranges. Range(size int) (rangeData Range, err error) diff --git a/ctx_test.go b/ctx_test.go index b4c0c55d38..08889023cc 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -2,6 +2,7 @@ // 🤖 Github Repository: https://github.com/gofiber/fiber // 📌 API Documentation: https://docs.gofiber.io +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package fiber import ( @@ -404,6 +405,7 @@ func Test_Ctx_UserContext(t *testing.T) { // go test -run Test_Ctx_SetUserContext func Test_Ctx_SetUserContext(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -416,6 +418,7 @@ func Test_Ctx_SetUserContext(t *testing.T) { // go test -run Test_Ctx_UserContext_Multiple_Requests func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) { + t.Parallel() testKey := struct{}{} testValue := "foobar-value" @@ -641,12 +644,13 @@ func Test_Ctx_FormFile(t *testing.T) { f, err := fh.Open() require.NoError(t, err) + defer func() { + require.Equal(t, nil, f.Close()) + }() b := new(bytes.Buffer) _, err = io.Copy(b, f) require.NoError(t, err) - - f.Close() require.Equal(t, "hello world", b.String()) return nil }) @@ -659,8 +663,7 @@ func Test_Ctx_FormFile(t *testing.T) { _, err = ioWriter.Write([]byte("hello world")) require.NoError(t, err) - - writer.Close() + require.NoError(t, writer.Close()) req := httptest.NewRequest(MethodPost, "/test", body) req.Header.Set(HeaderContentType, writer.FormDataContentType()) @@ -683,10 +686,9 @@ func Test_Ctx_FormValue(t *testing.T) { body := &bytes.Buffer{} writer := multipart.NewWriter(body) - require.Nil(t, writer.WriteField("name", "john")) + require.Nil(t, writer.Close()) - writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())) req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) @@ -867,6 +869,97 @@ func Benchmark_Ctx_Host(b *testing.B) { require.Equal(b, "google.com", host) } +// go test -run Test_Ctx_IsProxyTrusted +func Test_Ctx_IsProxyTrusted(t *testing.T) { + t.Parallel() + + { + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + require.True(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: false, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.True(t, c.IsProxyTrusted()) + } + + { + app := New(Config{ + EnableTrustedProxyCheck: true, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{}, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{ + "127.0.0.1", + }, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{ + "127.0.0.1/8", + }, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{ + "0.0.0.0", + }, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.True(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{ + "0.0.0.1/31", + }, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.True(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustedProxies: []string{ + "0.0.0.1/31junk", + }, + }) + c := app.NewCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } +} + // go test -run Test_Ctx_Hostname func Test_Ctx_Hostname(t *testing.T) { t.Parallel() @@ -998,7 +1091,7 @@ func Test_Ctx_IP(t *testing.T) { func Test_Ctx_IP_ProxyHeader(t *testing.T) { t.Parallel() - // make sure that the same behaviour exists for different proxy header names + // make sure that the same behavior exists for different proxy header names proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor} for _, proxyHeaderName := range proxyHeaderNames { @@ -1030,7 +1123,7 @@ func Test_Ctx_IP_ProxyHeader(t *testing.T) { 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 + // make sure that the same behavior exists for different proxy header names proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor} for _, proxyHeaderName := range proxyHeaderNames { @@ -1297,6 +1390,7 @@ func Benchmark_Ctx_Is(b *testing.B) { // go test -run Test_Ctx_Locals func Test_Ctx_Locals(t *testing.T) { + t.Parallel() app := New() app.Use(func(c Ctx) error { c.Locals("john", "doe") @@ -1357,35 +1451,45 @@ func Test_Ctx_ClientHelloInfo(t *testing.T) { }) // Test without TLS handler - resp, _ := app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) - body, _ := io.ReadAll(resp.Body) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) + require.NoError(t, err) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) require.Equal(t, []byte("ClientHelloInfo is nil"), body) // Test with TLS Handler const ( - PSSWithSHA256 = 0x0804 - VersionTLS13 = 0x0304 + pssWithSHA256 = 0x0804 + versionTLS13 = 0x0304 ) app.tlsHandler = &TLSHandler{clientHelloInfo: &tls.ClientHelloInfo{ ServerName: "example.golang", - SignatureSchemes: []tls.SignatureScheme{PSSWithSHA256}, - SupportedVersions: []uint16{VersionTLS13}, + SignatureSchemes: []tls.SignatureScheme{pssWithSHA256}, + SupportedVersions: []uint16{versionTLS13}, }} // Test ServerName - resp, _ = app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) - body, _ = io.ReadAll(resp.Body) + resp, err = app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil)) + require.NoError(t, err) + + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) 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)) + resp, err = app.Test(httptest.NewRequest(MethodGet, "/SignatureSchemes", nil)) + require.NoError(t, err) + + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + 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)) + resp, err = app.Test(httptest.NewRequest(MethodGet, "/SupportedVersions", nil)) + require.NoError(t, err) + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "["+strconv.Itoa(versionTLS13)+"]", string(body)) } // go test -run Test_Ctx_InvalidMethod @@ -1422,8 +1526,8 @@ func Test_Ctx_MultipartForm(t *testing.T) { writer := multipart.NewWriter(body) require.Nil(t, writer.WriteField("name", "john")) + require.NoError(t, writer.Close()) - writer.Close() req := httptest.NewRequest(MethodPost, "/test", body) req.Header.Set(HeaderContentType, fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())) req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes()))) @@ -1438,8 +1542,8 @@ func Benchmark_Ctx_MultipartForm(b *testing.B) { app := New() app.Post("/", func(c Ctx) error { - _, _ = c.MultipartForm() - return nil + _, err := c.MultipartForm() + return err }) c := &fasthttp.RequestCtx{} @@ -1590,6 +1694,7 @@ func Test_Ctx_Path(t *testing.T) { // go test -run Test_Ctx_Protocol func Test_Ctx_Protocol(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -1625,23 +1730,31 @@ func Test_Ctx_Scheme(t *testing.T) { c := app.NewCtx(freq) - c.Request().Header.Set(HeaderXForwardedProto, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) + c.Request().Header.Reset() + + c.Request().Header.Set(HeaderXForwardedProto, "https, http") + require.Equal(t, schemeHTTPS, c.Scheme()) + c.Request().Header.Reset() + + c.Request().Header.Set(HeaderXForwardedProtocol, "https, http") + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - require.Equal(t, "https", c.Scheme()) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) } // go test -v -run=^$ -bench=Benchmark_Ctx_Scheme -benchmem -count=4 @@ -1664,23 +1777,23 @@ func Test_Ctx_Scheme_TrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}}) c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - require.Equal(t, "https", c.Scheme()) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) } // go test -run Test_Ctx_Scheme_TrustedProxyRange @@ -1689,23 +1802,23 @@ func Test_Ctx_Scheme_TrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - require.Equal(t, "https", c.Scheme()) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, "https") - require.Equal(t, "https", c.Scheme()) + c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + require.Equal(t, schemeHTTPS, c.Scheme()) c.Request().Header.Reset() - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) } // go test -run Test_Ctx_Scheme_UntrustedProxyRange @@ -1714,23 +1827,23 @@ func Test_Ctx_Scheme_UntrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.1.1.1/30"}}) c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) } // go test -run Test_Ctx_Scheme_UnTrustedProxy @@ -1739,23 +1852,23 @@ func Test_Ctx_Scheme_UnTrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}}) c := app.NewCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() c.Request().Header.Set(HeaderXForwardedSsl, "on") - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, "https") - require.Equal(t, "http", c.Scheme()) + c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + require.Equal(t, schemeHTTP, c.Scheme()) c.Request().Header.Reset() - require.Equal(t, "http", c.Scheme()) + require.Equal(t, schemeHTTP, c.Scheme()) } // go test -run Test_Ctx_Query @@ -1770,6 +1883,20 @@ func Test_Ctx_Query(t *testing.T) { require.Equal(t, "default", c.Query("unknown", "default")) } +func Test_Ctx_QueryInt(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + + c.Request().URI().SetQueryString("search=john&age=20&id=") + require.Equal(t, 0, c.QueryInt("foo")) + require.Equal(t, 20, c.QueryInt("age", 12)) + require.Equal(t, 0, c.QueryInt("search")) + require.Equal(t, 1, c.QueryInt("search", 1)) + require.Equal(t, 0, c.QueryInt("id")) + require.Equal(t, 2, c.QueryInt("id", 2)) +} + // go test -run Test_Ctx_Range func Test_Ctx_Range(t *testing.T) { t.Parallel() @@ -1856,7 +1983,12 @@ func Test_Ctx_SaveFile(t *testing.T) { tempFile, err := os.CreateTemp(os.TempDir(), "test-") require.NoError(t, err) - defer os.Remove(tempFile.Name()) + defer func(file *os.File) { + err := file.Close() + require.NoError(t, err) + err = os.Remove(file.Name()) + require.NoError(t, err) + }(tempFile) err = c.SaveFile(fh, tempFile.Name()) require.NoError(t, err) @@ -1874,7 +2006,7 @@ func Test_Ctx_SaveFile(t *testing.T) { _, err = ioWriter.Write([]byte("hello world")) require.NoError(t, err) - writer.Close() + require.NoError(t, writer.Close()) req := httptest.NewRequest(MethodPost, "/test", body) req.Header.Set("Content-Type", writer.FormDataContentType()) @@ -1916,7 +2048,7 @@ func Test_Ctx_SaveFileToStorage(t *testing.T) { _, err = ioWriter.Write([]byte("hello world")) require.NoError(t, err) - writer.Close() + require.NoError(t, writer.Close()) req := httptest.NewRequest(MethodPost, "/test", body) req.Header.Set("Content-Type", writer.FormDataContentType()) @@ -2001,7 +2133,9 @@ func Test_Ctx_Download(t *testing.T) { f, err := os.Open("./ctx.go") require.NoError(t, err) - defer f.Close() + defer func() { + require.NoError(t, f.Close()) + }() expect, err := io.ReadAll(f) require.NoError(t, err) @@ -2020,7 +2154,9 @@ func Test_Ctx_SendFile(t *testing.T) { // fetch file content f, err := os.Open("./ctx.go") require.NoError(t, err) - defer f.Close() + defer func() { + require.NoError(t, nil, f.Close()) + }() expectFileContent, err := io.ReadAll(f) require.NoError(t, err) // fetch file info for the not modified test case @@ -2066,7 +2202,7 @@ func Test_Ctx_SendFile_404(t *testing.T) { return err }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, StatusNotFound, resp.StatusCode) } @@ -2104,11 +2240,11 @@ func Test_Ctx_SendFile_Immutable(t *testing.T) { for _, endpoint := range endpointsForTest { t.Run(endpoint, func(t *testing.T) { // 1st try - resp, err := app.Test(httptest.NewRequest("GET", endpoint, nil)) + resp, err := app.Test(httptest.NewRequest(MethodGet, endpoint, nil)) require.NoError(t, err) require.Equal(t, StatusOK, resp.StatusCode) // 2nd try - resp, err = app.Test(httptest.NewRequest("GET", endpoint, nil)) + resp, err = app.Test(httptest.NewRequest(MethodGet, endpoint, nil)) require.NoError(t, err) require.Equal(t, StatusOK, resp.StatusCode) }) @@ -2126,9 +2262,9 @@ func Test_Ctx_SendFile_RestoreOriginalURL(t *testing.T) { return err }) - _, err1 := app.Test(httptest.NewRequest("GET", "/?test=true", nil)) + _, err1 := app.Test(httptest.NewRequest(MethodGet, "/?test=true", nil)) // second request required to confirm with zero allocation - _, err2 := app.Test(httptest.NewRequest("GET", "/?test=true", nil)) + _, err2 := app.Test(httptest.NewRequest(MethodGet, "/?test=true", nil)) require.Nil(t, err1) require.Nil(t, err2) @@ -2334,6 +2470,7 @@ func Test_Ctx_Location(t *testing.T) { // go test -run Test_Ctx_Next func Test_Ctx_Next(t *testing.T) { + t.Parallel() app := New() app.Use("/", func(c Ctx) error { return c.Next() @@ -2350,6 +2487,7 @@ func Test_Ctx_Next(t *testing.T) { // go test -run Test_Ctx_Next_Error func Test_Ctx_Next_Error(t *testing.T) { + t.Parallel() app := New() app.Use("/", func(c Ctx) error { c.Set("X-Next-Result", "Works") @@ -2371,12 +2509,12 @@ func Test_Ctx_Render(t *testing.T) { err := c.Render("./.github/testdata/index.tmpl", Map{ "Title": "Hello, World!", }) + require.NoError(t, err) buf := bytebufferpool.Get() - _, _ = buf.WriteString("overwrite") + _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail defer bytebufferpool.Put(buf) - require.NoError(t, err) require.Equal(t, "

Hello, World!

", string(c.Response().Body())) err = c.Render("./.github/testdata/template-non-exists.html", nil) @@ -2396,12 +2534,12 @@ func Test_Ctx_RenderWithoutLocals(t *testing.T) { c.Locals("Title", "Hello, World!") err := c.Render("./.github/testdata/index.tmpl", Map{}) + require.NoError(t, err) buf := bytebufferpool.Get() - _, _ = buf.WriteString("overwrite") + _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail defer bytebufferpool.Put(buf) - require.NoError(t, err) require.Equal(t, "

", string(c.Response().Body())) } @@ -2415,12 +2553,12 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { c.Locals("Title", "Hello, World!") err := c.Render("./.github/testdata/index.tmpl", Map{}) + require.NoError(t, err) buf := bytebufferpool.Get() - _, _ = buf.WriteString("overwrite") + _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail defer bytebufferpool.Put(buf) - require.NoError(t, err) require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } @@ -2438,17 +2576,37 @@ 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") + _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail defer bytebufferpool.Put(buf) require.NoError(t, err) require.Equal(t, "

Hello, World!

", string(c.Response().Body())) +} + +func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) { + t.Parallel() + app := New() + c := app.NewCtx(&fasthttp.RequestCtx{}) + + err := c.BindVars(Map{ + "Title": "Hello, World!", + }) + require.NoError(t, err) + err = c.Render("./.github/testdata/index.tmpl", Map{ + "Title": "Hello from Fiber!", + }) + require.NoError(t, err) + + buf := bytebufferpool.Get() + _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail + defer bytebufferpool.Put(buf) + + require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) { t.Parallel() - app := New(Config{ PassLocalsToViews: true, }) @@ -2466,6 +2624,7 @@ func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) { require.NoError(t, err) require.Equal(t, "

Hello, World! Test

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

Hello, World! Test

", string(c.Response().Body())) } func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { @@ -2566,6 +2725,7 @@ func Benchmark_Ctx_RenderBindVars(b *testing.B) { // go test -run Test_Ctx_RestartRouting func Test_Ctx_RestartRouting(t *testing.T) { + t.Parallel() app := New() calls := 0 app.Get("/", func(c Ctx) error { @@ -2583,9 +2743,9 @@ func Test_Ctx_RestartRouting(t *testing.T) { // go test -run Test_Ctx_RestartRoutingWithChangedPath func Test_Ctx_RestartRoutingWithChangedPath(t *testing.T) { + t.Parallel() app := New() - executedOldHandler := false - executedNewHandler := false + var executedOldHandler, executedNewHandler bool app.Get("/old", func(c Ctx) error { c.Path("/new") @@ -2609,6 +2769,7 @@ func Test_Ctx_RestartRoutingWithChangedPath(t *testing.T) { // go test -run Test_Ctx_RestartRoutingWithChangedPathAnd404 func Test_Ctx_RestartRoutingWithChangedPathAndCatchAll(t *testing.T) { + t.Parallel() app := New() app.Get("/new", func(c Ctx) error { return nil @@ -2634,10 +2795,18 @@ type testTemplateEngine struct { func (t *testTemplateEngine) Render(w io.Writer, name string, bind any, layout ...string) error { if len(layout) == 0 { - return t.templates.ExecuteTemplate(w, name, bind) + if err := t.templates.ExecuteTemplate(w, name, bind); err != nil { + return fmt.Errorf("failed to execute template without layout: %w", err) + } + return nil + } + if err := t.templates.ExecuteTemplate(w, name, bind); err != nil { + return fmt.Errorf("failed to execute template: %w", err) } - _ = t.templates.ExecuteTemplate(w, name, bind) - return t.templates.ExecuteTemplate(w, layout[0], bind) + if err := t.templates.ExecuteTemplate(w, layout[0], bind); err != nil { + return fmt.Errorf("failed to execute template with layout: %w", err) + } + return nil } func (t *testTemplateEngine) Load() error { @@ -2650,6 +2819,7 @@ func (t *testTemplateEngine) Load() error { // go test -run Test_Ctx_Render_Engine func Test_Ctx_Render_Engine(t *testing.T) { + t.Parallel() engine := &testTemplateEngine{} require.Equal(t, nil, engine.Load()) app := New() @@ -2665,6 +2835,7 @@ func Test_Ctx_Render_Engine(t *testing.T) { // go test -run Test_Ctx_Render_Engine_With_View_Layout func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) { + t.Parallel() engine := &testTemplateEngine{} require.Equal(t, nil, engine.Load()) app := New(Config{ViewsLayout: "main.tmpl"}) @@ -2712,14 +2883,17 @@ func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) { for n := 0; n < b.N; n++ { 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) { + t.Parallel() + t.Run("case insensitive", func(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) app.Get("/user/:name", func(c Ctx) error { @@ -2736,6 +2910,7 @@ func Test_Ctx_Get_Location_From_Route_name(t *testing.T) { }) t.Run("case sensitive", func(t *testing.T) { + t.Parallel() app := New(Config{CaseSensitive: true}) c := app.NewCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) @@ -2755,6 +2930,7 @@ func Test_Ctx_Get_Location_From_Route_name(t *testing.T) { // go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy func Test_Ctx_Get_Location_From_Route_name_Optional_greedy(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2773,6 +2949,7 @@ func Test_Ctx_Get_Location_From_Route_name_Optional_greedy(t *testing.T) { // go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param func Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2790,14 +2967,15 @@ func Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param(t *testing. type errorTemplateEngine struct{} -func (t errorTemplateEngine) Render(w io.Writer, name string, bind any, layout ...string) error { +func (errorTemplateEngine) Render(_ io.Writer, _ string, _ any, _ ...string) error { return errors.New("errorTemplateEngine") } -func (t errorTemplateEngine) Load() error { return nil } +func (errorTemplateEngine) Load() error { return nil } // go test -run Test_Ctx_Render_Engine_Error func Test_Ctx_Render_Engine_Error(t *testing.T) { + t.Parallel() app := New() app.config.Views = errorTemplateEngine{} c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -2809,10 +2987,12 @@ func Test_Ctx_Render_Engine_Error(t *testing.T) { // go test -run Test_Ctx_Render_Go_Template func Test_Ctx_Render_Go_Template(t *testing.T) { t.Parallel() - file, err := os.CreateTemp(os.TempDir(), "fiber") require.NoError(t, err) - defer os.Remove(file.Name()) + defer func() { + err := os.Remove(file.Name()) + require.NoError(t, err) + }() _, err = file.Write([]byte("template")) require.NoError(t, err) @@ -3134,7 +3314,6 @@ func Benchmark_Ctx_SendString_B(b *testing.B) { // go test -run Test_Ctx_BodyStreamWriter func Test_Ctx_BodyStreamWriter(t *testing.T) { t.Parallel() - ctx := &fasthttp.RequestCtx{} ctx.SetBodyStreamWriter(func(w *bufio.Writer) { @@ -3185,7 +3364,6 @@ func Benchmark_Ctx_BodyStreamWriter(b *testing.B) { func Test_Ctx_String(t *testing.T) { t.Parallel() - app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) @@ -3194,8 +3372,8 @@ func Test_Ctx_String(t *testing.T) { func TestCtx_ParamsInt(t *testing.T) { // Create a test context and set some strings (or params) - // create a fake app to be used within this test + t.Parallel() app := New() // Create some test endpoints @@ -3295,6 +3473,7 @@ func TestCtx_ParamsInt(t *testing.T) { // go test -run Test_Ctx_GetRespHeader func Test_Ctx_GetRespHeader(t *testing.T) { + t.Parallel() app := New() c := app.NewCtx(&fasthttp.RequestCtx{}) diff --git a/error_test.go b/error_test.go index 7fce3c12aa..02096cd3db 100644 --- a/error_test.go +++ b/error_test.go @@ -1,67 +1,76 @@ package fiber import ( + "encoding/json" "errors" "testing" - jerrors "encoding/json" - "github.com/gofiber/fiber/v3/internal/schema" "github.com/stretchr/testify/require" ) func TestConversionError(t *testing.T) { + t.Parallel() ok := errors.As(ConversionError{}, &schema.ConversionError{}) require.True(t, ok) } func TestUnknownKeyError(t *testing.T) { + t.Parallel() ok := errors.As(UnknownKeyError{}, &schema.UnknownKeyError{}) require.True(t, ok) } func TestEmptyFieldError(t *testing.T) { + t.Parallel() ok := errors.As(EmptyFieldError{}, &schema.EmptyFieldError{}) require.True(t, ok) } func TestMultiError(t *testing.T) { + t.Parallel() ok := errors.As(MultiError{}, &schema.MultiError{}) require.True(t, ok) } func TestInvalidUnmarshalError(t *testing.T) { - var e *jerrors.InvalidUnmarshalError + t.Parallel() + var e *json.InvalidUnmarshalError ok := errors.As(&InvalidUnmarshalError{}, &e) require.True(t, ok) } func TestMarshalerError(t *testing.T) { - var e *jerrors.MarshalerError + t.Parallel() + var e *json.MarshalerError ok := errors.As(&MarshalerError{}, &e) require.True(t, ok) } func TestSyntaxError(t *testing.T) { - var e *jerrors.SyntaxError + t.Parallel() + var e *json.SyntaxError ok := errors.As(&SyntaxError{}, &e) require.True(t, ok) } func TestUnmarshalTypeError(t *testing.T) { - var e *jerrors.UnmarshalTypeError + t.Parallel() + var e *json.UnmarshalTypeError ok := errors.As(&UnmarshalTypeError{}, &e) require.True(t, ok) } func TestUnsupportedTypeError(t *testing.T) { - var e *jerrors.UnsupportedTypeError + t.Parallel() + var e *json.UnsupportedTypeError ok := errors.As(&UnsupportedTypeError{}, &e) require.True(t, ok) } func TestUnsupportedValeError(t *testing.T) { - var e *jerrors.UnsupportedValueError + t.Parallel() + var e *json.UnsupportedValueError ok := errors.As(&UnsupportedValueError{}, &e) require.True(t, ok) } diff --git a/go.mod b/go.mod index fec575483a..7c330458c8 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,26 @@ module github.com/gofiber/fiber/v3 -go 1.19 +go 1.20 require ( github.com/gofiber/fiber/v2 v2.40.1 github.com/gofiber/utils/v2 v2.0.0-beta.1 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/mattn/go-isatty v0.0.17 github.com/stretchr/testify v1.8.1 github.com/tinylib/msgp v1.1.6 github.com/valyala/bytebufferpool v1.0.0 - github.com/valyala/fasthttp v1.42.0 + github.com/valyala/fasthttp v1.44.0 ) require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/klauspost/compress v1.15.12 // indirect + github.com/klauspost/compress v1.15.15 // indirect github.com/philhofer/fwd v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.2.0 // indirect + golang.org/x/sys v0.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d1f071c00b..075184ead7 100644 --- a/go.sum +++ b/go.sum @@ -10,12 +10,13 @@ github.com/gofiber/utils/v2 v2.0.0-beta.1/go.mod h1:CG89nDoIkEFIJaw5LdLO9AmBM11o 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.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/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= 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/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -31,10 +32,8 @@ 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.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= -github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= -github.com/valyala/fasthttp v1.42.0 h1:LBMyqvJR8DEBgN79oI8dGbkuj5Lm9jbHESxH131TTN8= -github.com/valyala/fasthttp v1.42.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= +github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= +github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -58,10 +57,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.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= diff --git a/group.go b/group.go index 1470e970d5..50753c177d 100644 --- a/group.go +++ b/group.go @@ -180,7 +180,6 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router { } return newGrp - } // Route is used to define routes with a common prefix inside the common function. diff --git a/helpers.go b/helpers.go index ebf976f904..7754abf9df 100644 --- a/helpers.go +++ b/helpers.go @@ -7,6 +7,7 @@ package fiber import ( "bytes" "crypto/tls" + "fmt" "io" "log" "net" @@ -21,9 +22,8 @@ import ( "github.com/valyala/fasthttp" ) -/* #nosec */ -// getTlsConfig returns a net listener's tls config -func getTlsConfig(ln net.Listener) *tls.Config { +// getTLSConfig returns a net listener's tls config +func getTLSConfig(ln net.Listener) *tls.Config { // Get listener type pointer := reflect.ValueOf(ln) @@ -34,12 +34,16 @@ func getTlsConfig(ln net.Listener) *tls.Config { // Get private field from value if field := val.FieldByName("config"); field.Type() != nil { // Copy value from pointer field (unsafe) - newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) // #nosec G103 + newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe. if newval.Type() != nil { // Get element from pointer if elem := newval.Elem(); elem.Type() != nil { // Cast value to *tls.Config - return elem.Interface().(*tls.Config) + c, ok := elem.Interface().(*tls.Config) + if !ok { + panic(fmt.Errorf("failed to type-assert to *tls.Config")) + } + return c } } } @@ -50,19 +54,21 @@ func getTlsConfig(ln net.Listener) *tls.Config { } // readContent opens a named file and read content from it -func readContent(rf io.ReaderFrom, name string) (n int64, err error) { +func readContent(rf io.ReaderFrom, name string) (int64, error) { // Read file f, err := os.Open(filepath.Clean(name)) if err != nil { - return 0, err + return 0, fmt.Errorf("failed to open: %w", err) } - // #nosec G307 defer func() { if err = f.Close(); err != nil { log.Printf("Error closing file: %s\n", err) } }() - return rf.ReadFrom(f) + if n, err := rf.ReadFrom(f); err != nil { + return n, fmt.Errorf("failed to read: %w", err) + } + return 0, nil } // quoteString escape special characters in a given string @@ -75,7 +81,9 @@ func (app *App) quoteString(raw string) string { } // Scan stack if other methods match the request -func (app *App) methodExist(c *DefaultCtx) (exist bool) { +func (app *App) methodExist(c *DefaultCtx) bool { + var exists bool + methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method @@ -106,7 +114,7 @@ func (app *App) methodExist(c *DefaultCtx) (exist bool) { // No match, next route if match { // We matched - exist = true + exists = true // Add method to Allow header c.Append(HeaderAllow, methods[i]) // Break stack loop @@ -114,11 +122,12 @@ func (app *App) methodExist(c *DefaultCtx) (exist bool) { } } } - return + return exists } // Scan stack if other methods match the request -func (app *App) methodExistCustom(c CustomCtx) (exist bool) { +func (app *App) methodExistCustom(c CustomCtx) bool { + var exists bool methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method @@ -149,7 +158,7 @@ func (app *App) methodExistCustom(c CustomCtx) (exist bool) { // No match, next route if match { // We matched - exist = true + exists = true // Add method to Allow header c.Append(HeaderAllow, methods[i]) // Break stack loop @@ -157,7 +166,7 @@ func (app *App) methodExistCustom(c CustomCtx) (exist bool) { } } } - return + return exists } // uniqueRouteStack drop all not unique routes from the slice @@ -232,7 +241,7 @@ func getOffer(header string, offers ...string) string { return "" } -func matchEtag(s string, etag string) bool { +func matchEtag(s, etag string) bool { if s == etag || s == "W/"+etag || "W/"+s == etag { return true } @@ -266,7 +275,7 @@ func (app *App) isEtagStale(etag string, noneMatchBytes []byte) bool { return !matchEtag(app.getString(noneMatchBytes[start:end]), etag) } -func parseAddr(raw string) (host, port string) { +func parseAddr(raw string) (string, string) { //nolint:revive // Returns (host, port) if i := strings.LastIndex(raw, ":"); i != -1 { return raw[:i], raw[i+1:] } @@ -306,21 +315,21 @@ type testConn struct { w bytes.Buffer } -func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) } -func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) } -func (c *testConn) Close() error { return nil } +func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) } //nolint:wrapcheck // This must not be wrapped +func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) } //nolint:wrapcheck // This must not be wrapped +func (*testConn) Close() error { return nil } -func (c *testConn) LocalAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } -func (c *testConn) RemoteAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } -func (c *testConn) SetDeadline(_ time.Time) error { return nil } -func (c *testConn) SetReadDeadline(_ time.Time) error { return nil } -func (c *testConn) SetWriteDeadline(_ time.Time) error { return nil } +func (*testConn) LocalAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } +func (*testConn) RemoteAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } +func (*testConn) SetDeadline(_ time.Time) error { return nil } +func (*testConn) SetReadDeadline(_ time.Time) error { return nil } +func (*testConn) SetWriteDeadline(_ time.Time) error { return nil } -var getStringImmutable = func(b []byte) string { +func getStringImmutable(b []byte) string { return string(b) } -var getBytesImmutable = func(s string) (b []byte) { +func getBytesImmutable(s string) []byte { return []byte(s) } @@ -328,6 +337,7 @@ var getBytesImmutable = func(s string) (b []byte) { func (app *App) methodInt(s string) int { // For better performance if len(app.configured.RequestMethods) == 0 { + // TODO: Use iota instead switch s { case MethodGet: return 0 @@ -384,8 +394,7 @@ func IsMethodIdempotent(m string) bool { } switch m { - case MethodPut, - MethodDelete: + case MethodPut, MethodDelete: return true default: return false @@ -705,7 +714,7 @@ const ( ConstraintBool = "bool" ConstraintFloat = "float" ConstraintAlpha = "alpha" - ConstraintGuid = "guid" + ConstraintGuid = "guid" //nolint:revive,stylecheck // TODO: Rename to "ConstraintGUID" in v3 ConstraintMinLen = "minLen" ConstraintMaxLen = "maxLen" ConstraintLen = "len" @@ -719,3 +728,12 @@ const ( ConstraintDatetime = "datetime" ConstraintRegex = "regex" ) + +func IndexRune(str string, needle int32) bool { + for _, b := range str { + if b == needle { + return true + } + } + return false +} diff --git a/helpers_test.go b/helpers_test.go index bba282b229..b5f607be0b 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -16,6 +16,7 @@ import ( ) func Test_Utils_UniqueRouteStack(t *testing.T) { + t.Parallel() route1 := &Route{} route2 := &Route{} route3 := &Route{} @@ -92,6 +93,7 @@ func Benchmark_Utils_Unescape(b *testing.B) { } func Test_Utils_Parse_Address(t *testing.T) { + t.Parallel() testCases := []struct { addr, host, port string }{ @@ -114,6 +116,7 @@ func Test_Utils_GetOffset(t *testing.T) { } func Test_Utils_TestConn_Deadline(t *testing.T) { + t.Parallel() conn := &testConn{} require.Nil(t, conn.SetDeadline(time.Time{})) require.Nil(t, conn.SetReadDeadline(time.Time{})) @@ -121,6 +124,7 @@ func Test_Utils_TestConn_Deadline(t *testing.T) { } func Test_Utils_IsNoCache(t *testing.T) { + t.Parallel() testCases := []struct { string bool @@ -192,12 +196,3 @@ func Benchmark_SlashRecognition(b *testing.B) { require.True(b, result) }) } - -func IndexRune(str string, needle int32) bool { - for _, b := range str { - if b == needle { - return true - } - } - return false -} diff --git a/hooks.go b/hooks.go index b2766a8cab..c66e49fde8 100644 --- a/hooks.go +++ b/hooks.go @@ -1,14 +1,20 @@ package fiber +import ( + "log" +) + // 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 ( + OnRouteHandler = func(Route) error + OnNameHandler = OnRouteHandler + OnGroupHandler = func(Group) error + OnGroupNameHandler = OnGroupHandler + OnListenHandler = func() error + OnShutdownHandler = OnListenHandler + OnForkHandler = func(int) error + OnMountHandler = func(*App) error +) // Hooks is a struct to use it with App. type Hooks struct { @@ -180,13 +186,17 @@ func (h *Hooks) executeOnListenHooks() error { func (h *Hooks) executeOnShutdownHooks() { for _, v := range h.onShutdown { - _ = v() + if err := v(); err != nil { + log.Printf("failed to call shutdown hook: %v\n", err) + } } } func (h *Hooks) executeOnForkHooks(pid int) { for _, v := range h.onFork { - _ = v(pid) + if err := v(pid); err != nil { + log.Printf("failed to call fork hook: %v\n", err) + } } } diff --git a/hooks_test.go b/hooks_test.go index b4cb642014..91a9b4775f 100644 --- a/hooks_test.go +++ b/hooks_test.go @@ -10,13 +10,12 @@ import ( "github.com/valyala/bytebufferpool" ) -var testSimpleHandler = func(c Ctx) error { +func testSimpleHandler(c Ctx) error { return c.SendString("simple") } func Test_Hook_OnRoute(t *testing.T) { t.Parallel() - app := New() app.Hooks().OnRoute(func(r Route) error { @@ -35,7 +34,6 @@ func Test_Hook_OnRoute(t *testing.T) { func Test_Hook_OnRoute_Mount(t *testing.T) { t.Parallel() - app := New() subApp := New() app.Use("/sub", subApp) @@ -58,7 +56,6 @@ func Test_Hook_OnRoute_Mount(t *testing.T) { func Test_Hook_OnName(t *testing.T) { t.Parallel() - app := New() buf := bytebufferpool.Get() @@ -84,7 +81,6 @@ func Test_Hook_OnName(t *testing.T) { func Test_Hook_OnName_Error(t *testing.T) { t.Parallel() - app := New() defer func() { if err := recover(); err != nil { @@ -101,7 +97,6 @@ func Test_Hook_OnName_Error(t *testing.T) { func Test_Hook_OnGroup(t *testing.T) { t.Parallel() - app := New() buf := bytebufferpool.Get() @@ -121,7 +116,6 @@ func Test_Hook_OnGroup(t *testing.T) { func Test_Hook_OnGroup_Mount(t *testing.T) { t.Parallel() - app := New() micro := New() micro.Use("/john", app) @@ -139,7 +133,6 @@ func Test_Hook_OnGroup_Mount(t *testing.T) { func Test_Hook_OnGroupName(t *testing.T) { t.Parallel() - app := New() buf := bytebufferpool.Get() @@ -161,7 +154,6 @@ func Test_Hook_OnGroupName(t *testing.T) { func Test_Hook_OnGroupName_Error(t *testing.T) { t.Parallel() - app := New() defer func() { if err := recover(); err != nil { @@ -179,7 +171,6 @@ func Test_Hook_OnGroupName_Error(t *testing.T) { func Test_Hook_OnShutdown(t *testing.T) { t.Parallel() - app := New() buf := bytebufferpool.Get() @@ -221,12 +212,12 @@ func Test_Hook_OnListen(t *testing.T) { } func Test_Hook_OnHook(t *testing.T) { + app := New() + // Reset test var testPreforkMaster = true testOnPrefork = true - app := New() - go func() { time.Sleep(1000 * time.Millisecond) require.Nil(t, app.Shutdown()) @@ -242,7 +233,6 @@ func Test_Hook_OnHook(t *testing.T) { func Test_Hook_OnMount(t *testing.T) { t.Parallel() - app := New() app.Get("/", testSimpleHandler).Name("x") diff --git a/internal/memory/memory.go b/internal/memory/memory.go index fe23a2581e..829c2a98e6 100644 --- a/internal/memory/memory.go +++ b/internal/memory/memory.go @@ -73,28 +73,25 @@ func (s *Storage) gc(sleep time.Duration) { defer ticker.Stop() var expired []string - for { - select { - case <-ticker.C: - ts := atomic.LoadUint32(&utils.Timestamp) - expired = expired[:0] - s.RLock() - for key, v := range s.data { - if v.e != 0 && v.e <= ts { - expired = append(expired, key) - } + for range ticker.C { + ts := atomic.LoadUint32(&utils.Timestamp) + expired = expired[:0] + s.RLock() + for key, v := range s.data { + if v.e != 0 && v.e <= ts { + expired = append(expired, key) } - s.RUnlock() - s.Lock() - // Double-checked locking. - // We might have replaced the item in the meantime. - for i := range expired { - v := s.data[expired[i]] - if v.e != 0 && v.e <= ts { - delete(s.data, expired[i]) - } + } + s.RUnlock() + s.Lock() + // Double-checked locking. + // We might have replaced the item in the meantime. + for i := range expired { + v := s.data[expired[i]] + if v.e != 0 && v.e <= ts { + delete(s.data, expired[i]) } - s.Unlock() } + s.Unlock() } } diff --git a/internal/memory/memory_test.go b/internal/memory/memory_test.go index dfc1cb28c4..41183b9c9d 100644 --- a/internal/memory/memory_test.go +++ b/internal/memory/memory_test.go @@ -11,6 +11,7 @@ import ( // go test -run Test_Memory -v -race func Test_Memory(t *testing.T) { + t.Parallel() var store = New() var ( key = "john" diff --git a/internal/schema/encoder.go b/internal/schema/encoder.go index 4d03b154e8..849d7c0fec 100644 --- a/internal/schema/encoder.go +++ b/internal/schema/encoder.go @@ -94,7 +94,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { // Encode struct pointer types if the field is a valid pointer and a struct. if isValidStructPointer(v.Field(i)) { - e.encode(v.Field(i).Elem(), dst) + _ = e.encode(v.Field(i).Elem(), dst) continue } @@ -112,7 +112,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { } if v.Field(i).Type().Kind() == reflect.Struct { - e.encode(v.Field(i), dst) + _ = e.encode(v.Field(i), dst) continue } diff --git a/internal/storage/memory/memory_test.go b/internal/storage/memory/memory_test.go index 00dcf86b4a..b0a5b23c9f 100644 --- a/internal/storage/memory/memory_test.go +++ b/internal/storage/memory/memory_test.go @@ -11,6 +11,7 @@ import ( var testStore = New() func Test_Storage_Memory_Set(t *testing.T) { + t.Parallel() var ( key = "john" val = []byte("doe") @@ -21,6 +22,7 @@ func Test_Storage_Memory_Set(t *testing.T) { } func Test_Storage_Memory_Set_Override(t *testing.T) { + t.Parallel() var ( key = "john" val = []byte("doe") @@ -34,6 +36,7 @@ func Test_Storage_Memory_Set_Override(t *testing.T) { } func Test_Storage_Memory_Get(t *testing.T) { + t.Parallel() var ( key = "john" val = []byte("doe") @@ -48,6 +51,7 @@ func Test_Storage_Memory_Get(t *testing.T) { } func Test_Storage_Memory_Set_Expiration(t *testing.T) { + t.Parallel() var ( key = "john" val = []byte("doe") @@ -71,6 +75,7 @@ func Test_Storage_Memory_Get_Expired(t *testing.T) { } func Test_Storage_Memory_Get_NotExist(t *testing.T) { + t.Parallel() result, err := testStore.Get("notexist") require.NoError(t, err) @@ -78,6 +83,7 @@ func Test_Storage_Memory_Get_NotExist(t *testing.T) { } func Test_Storage_Memory_Delete(t *testing.T) { + t.Parallel() var ( key = "john" val = []byte("doe") @@ -95,6 +101,7 @@ func Test_Storage_Memory_Delete(t *testing.T) { } func Test_Storage_Memory_Reset(t *testing.T) { + t.Parallel() var ( val = []byte("doe") ) @@ -118,10 +125,12 @@ func Test_Storage_Memory_Reset(t *testing.T) { } func Test_Storage_Memory_Close(t *testing.T) { + t.Parallel() require.NoError(t, testStore.Close()) } func Test_Storage_Memory_Conn(t *testing.T) { + t.Parallel() require.True(t, testStore.Conn() != nil) } diff --git a/listen.go b/listen.go index 1ecaa87318..d05659276a 100644 --- a/listen.go +++ b/listen.go @@ -194,7 +194,7 @@ func (app *App) Listen(addr string, config ...ListenConfig) error { // Configure Listener ln, err := app.createListener(addr, tlsConfig, cfg) if err != nil { - return err + return fmt.Errorf("failed to listen: %w", err) } // prepare the server for the start @@ -268,7 +268,7 @@ func (app *App) createListener(addr string, tlsConfig *tls.Config, cfg ListenCon func (app *App) printMessages(cfg ListenConfig, ln net.Listener) { // Print startup message if !cfg.DisableStartupMessage { - app.startupMessage(ln.Addr().String(), getTlsConfig(ln) != nil, "", cfg) + app.startupMessage(ln.Addr().String(), getTLSConfig(ln) != nil, "", cfg) } // Print routes @@ -296,9 +296,9 @@ func (app *App) startupMessage(addr string, tls bool, pids string, cfg ListenCon } } - scheme := "http" + scheme := schemeHTTP if tls { - scheme = "https" + scheme = schemeHTTPS } isPrefork := "Disabled" @@ -389,7 +389,7 @@ func (app *App) printRoutesMessage() { var routes []RouteMessage for _, routeStack := range app.stack { for _, route := range routeStack { - var newRoute = RouteMessage{} + var newRoute RouteMessage newRoute.name = route.Name newRoute.method = route.Method newRoute.path = route.Path @@ -417,7 +417,7 @@ func (app *App) printRoutesMessage() { _, _ = 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() + _ = w.Flush() //nolint:errcheck // It is fine to ignore the error here } // shutdown goroutine diff --git a/listen_test.go b/listen_test.go index de482e4331..ab6b565dd9 100644 --- a/listen_test.go +++ b/listen_test.go @@ -217,13 +217,16 @@ func Test_Listener(t *testing.T) { } func Test_App_Listener_TLS_Listener(t *testing.T) { + t.Parallel() // Create tls certificate cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") if err != nil { require.NoError(t, err) } + //nolint:gosec // We're in a test so using old ciphers is fine config := &tls.Config{Certificates: []tls.Certificate{cer}} + //nolint:gosec // We're in a test so listening on all interfaces is fine ln, err := tls.Listen(NetworkTCP4, ":0", config) require.NoError(t, err) diff --git a/middleware/basicauth/basicauth_test.go b/middleware/basicauth/basicauth_test.go index 7656c9eb6e..8754333ece 100644 --- a/middleware/basicauth/basicauth_test.go +++ b/middleware/basicauth/basicauth_test.go @@ -1,13 +1,12 @@ package basicauth import ( + "encoding/base64" "fmt" "io" "net/http/httptest" "testing" - b64 "encoding/base64" - "github.com/gofiber/fiber/v3" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" @@ -16,7 +15,6 @@ import ( // go test -run Test_BasicAuth_Next func Test_BasicAuth_Next(t *testing.T) { t.Parallel() - app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -24,7 +22,7 @@ func Test_BasicAuth_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } @@ -75,9 +73,9 @@ func Test_Middleware_BasicAuth(t *testing.T) { for _, tt := range tests { // Base64 encode credentials for http auth header - creds := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", tt.username, tt.password))) + creds := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", tt.username, tt.password))) - req := httptest.NewRequest("GET", "/testauth", nil) + req := httptest.NewRequest(fiber.MethodGet, "/testauth", nil) req.Header.Add("Authorization", "Basic "+creds) resp, err := app.Test(req) require.NoError(t, err) @@ -109,7 +107,7 @@ func Benchmark_Middleware_BasicAuth(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") fctx.Request.Header.Set(fiber.HeaderAuthorization, "basic am9objpkb2U=") // john:doe diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 87712c1922..14ec063191 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -43,8 +43,8 @@ var ignoreHeaders = map[string]any{ "Trailers": nil, "Transfer-Encoding": nil, "Upgrade": nil, - "Content-Type": nil, // already stored explicitely by the cache manager - "Content-Encoding": nil, // already stored explicitely by the cache manager + "Content-Type": nil, // already stored explicitly by the cache manager + "Content-Encoding": nil, // already stored explicitly by the cache manager } // New creates a new middleware handler @@ -69,7 +69,7 @@ func New(config ...Config) fiber.Handler { // Create indexed heap for tracking expirations ( see heap.go ) heap := &indexedHeap{} // count stored bytes (sizes of response bodies) - var storedBytes uint = 0 + var storedBytes uint // Update timestamp in the configured interval go func() { @@ -81,10 +81,10 @@ func New(config ...Config) fiber.Handler { // Delete key from both manager and storage deleteKey := func(dkey string) { - manager.delete(dkey) + manager.del(dkey) // External storage saves body data with different key if cfg.Storage != nil { - manager.delete(dkey + "_body") + manager.del(dkey + "_body") } } @@ -205,7 +205,7 @@ func New(config ...Config) fiber.Handler { if cfg.StoreResponseHeaders { e.headers = make(map[string][]byte) c.Response().Header.VisitAll( - func(key []byte, value []byte) { + func(key, value []byte) { // create real copy keyS := string(key) if _, ok := ignoreHeaders[keyS]; !ok { diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 5567f0b998..1da4496f58 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "math" - "net/http" "net/http/httptest" "os" "strconv" @@ -36,10 +35,10 @@ func Test_Cache_CacheControl(t *testing.T) { return c.SendString("Hello, World!") }) - _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl)) } @@ -54,7 +53,7 @@ func Test_Cache_Expired(t *testing.T) { return c.SendString(fmt.Sprintf("%d", time.Now().UnixNano())) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -62,7 +61,7 @@ func Test_Cache_Expired(t *testing.T) { // Sleep until the cache is expired time.Sleep(3 * time.Second) - respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) + respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) require.NoError(t, err) @@ -72,7 +71,7 @@ func Test_Cache_Expired(t *testing.T) { } // Next response should be also cached - respCachedNextRound, err := app.Test(httptest.NewRequest("GET", "/", nil)) + respCachedNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) bodyCachedNextRound, err := io.ReadAll(respCachedNextRound.Body) require.NoError(t, err) @@ -93,11 +92,11 @@ func Test_Cache(t *testing.T) { return c.SendString(now) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err := app.Test(req) require.NoError(t, err) - cachedReq := httptest.NewRequest("GET", "/", nil) + cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil) cachedResp, err := app.Test(cachedReq) require.NoError(t, err) @@ -121,31 +120,31 @@ func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { }) // Request id = 1 - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err := app.Test(req) - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + require.NoError(t, err) + body, err := 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) + cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) cachedResp, err := app.Test(cachedReq) - defer cachedResp.Body.Close() - cachedBody, _ := io.ReadAll(cachedResp.Body) + require.NoError(t, err) + cachedBody, err := 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 := httptest.NewRequest(fiber.MethodGet, "/?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) + noCacheBody, err := io.ReadAll(noCacheResp.Body) require.NoError(t, err) require.Equal(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) require.Equal(t, []byte("2"), noCacheBody) @@ -153,21 +152,21 @@ func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { /* Check Test_Cache_WithETagAndNoCacheRequestDirective */ // Request id = 2 with Cache-Control: no-cache again - noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?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) + noCacheBody1, err := 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) + cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil) cachedResp1, err := app.Test(cachedReq1) - defer cachedResp1.Body.Close() - cachedBody1, _ := io.ReadAll(cachedResp1.Body) + require.NoError(t, err) + cachedBody1, err := io.ReadAll(cachedResp1.Body) require.NoError(t, err) require.Equal(t, cacheHit, cachedResp1.Header.Get("X-Cache")) require.Equal(t, []byte("2"), cachedBody1) @@ -189,7 +188,7 @@ func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { }) // Request id = 1 - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) @@ -200,7 +199,7 @@ func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { etagToken := resp.Header.Get("Etag") // Request id = 2 with ETag but without Cache-Control: no-cache - cachedReq := httptest.NewRequest("GET", "/?id=2", nil) + cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) cachedResp, err := app.Test(cachedReq) require.NoError(t, err) @@ -209,7 +208,7 @@ func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { // 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 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache) noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) noCacheResp, err := app.Test(noCacheReq) @@ -222,7 +221,7 @@ func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { etagToken = noCacheResp.Header.Get("Etag") // Request id = 2 with ETag and Cache-Control: no-cache again - noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache) noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) noCacheResp1, err := app.Test(noCacheReq1) @@ -232,7 +231,7 @@ func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { // 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) + cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil) cachedResp1, err := app.Test(cachedReq1) require.NoError(t, err) require.Equal(t, cacheHit, cachedResp1.Header.Get("X-Cache")) @@ -252,11 +251,11 @@ func Test_Cache_WithNoStoreRequestDirective(t *testing.T) { }) // Request id = 2 - noStoreReq := httptest.NewRequest("GET", "/?id=2", nil) + noStoreReq := httptest.NewRequest(fiber.MethodGet, "/?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) + noStoreBody, err := io.ReadAll(noStoreResp.Body) require.NoError(t, err) require.Equal(t, []byte("2"), noStoreBody) // Response not cached, returns updated response @@ -279,11 +278,11 @@ func Test_Cache_WithSeveralRequests(t *testing.T) { for runs := 0; runs < 10; runs++ { for i := 0; i < 10; i++ { func(id int) { - rsp, err := app.Test(httptest.NewRequest(http.MethodGet, fmt.Sprintf("/%d", id), nil)) + rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, fmt.Sprintf("/%d", id), nil)) require.NoError(t, err) - defer func(Body io.ReadCloser) { - err := Body.Close() + defer func(body io.ReadCloser) { + err := body.Close() require.NoError(t, err) }(rsp.Body) @@ -312,11 +311,11 @@ func Test_Cache_Invalid_Expiration(t *testing.T) { return c.SendString(now) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err := app.Test(req) require.NoError(t, err) - cachedReq := httptest.NewRequest("GET", "/", nil) + cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil) cachedResp, err := app.Test(cachedReq) require.NoError(t, err) @@ -343,25 +342,25 @@ func Test_Cache_Get(t *testing.T) { return c.SendString(c.Query("cache")) }) - resp, err := app.Test(httptest.NewRequest("POST", "/?cache=123", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?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("POST", "/?cache=12345", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil)) require.NoError(t, err) body, err = io.ReadAll(resp.Body) require.NoError(t, err) @@ -385,25 +384,25 @@ func Test_Cache_Post(t *testing.T) { return c.SendString(c.Query("cache")) }) - resp, err := app.Test(httptest.NewRequest("POST", "/?cache=123", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?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("POST", "/?cache=12345", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", 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=123", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil)) require.NoError(t, err) body, err = io.ReadAll(resp.Body) require.NoError(t, err) @@ -421,14 +420,14 @@ func Test_Cache_NothingToCache(t *testing.T) { return c.SendString(time.Now().String()) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) body, err := io.ReadAll(resp.Body) require.NoError(t, err) time.Sleep(500 * time.Millisecond) - respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) + respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) require.NoError(t, err) @@ -458,22 +457,22 @@ func Test_Cache_CustomNext(t *testing.T) { return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String()) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) body, err := io.ReadAll(resp.Body) require.NoError(t, err) - respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) + respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) bodyCached, err := io.ReadAll(respCached.Body) 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)) + _, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) require.NoError(t, err) - errRespCached, err := app.Test(httptest.NewRequest("GET", "/error", nil)) + errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) require.NoError(t, err) require.True(t, errRespCached.Header.Get(fiber.HeaderCacheControl) == "") } @@ -492,7 +491,7 @@ func Test_CustomKey(t *testing.T) { return c.SendString("hi") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) _, err := app.Test(req) require.NoError(t, err) require.True(t, called) @@ -506,7 +505,9 @@ func Test_CustomExpiration(t *testing.T) { var newCacheTime int app.Use(New(Config{ExpirationGenerator: func(c fiber.Ctx, cfg *Config) time.Duration { called = true - newCacheTime, _ = strconv.Atoi(c.GetRespHeader("Cache-Time", "600")) + var err error + newCacheTime, err = strconv.Atoi(c.GetRespHeader("Cache-Time", "600")) + require.NoError(t, err) return time.Second * time.Duration(newCacheTime) }})) @@ -516,7 +517,7 @@ func Test_CustomExpiration(t *testing.T) { return c.SendString(now) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.True(t, called) require.Equal(t, 1, newCacheTime) @@ -524,7 +525,7 @@ func Test_CustomExpiration(t *testing.T) { // Sleep until the cache is expired time.Sleep(1 * time.Second) - cachedResp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + cachedResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) body, err := io.ReadAll(resp.Body) @@ -537,7 +538,7 @@ func Test_CustomExpiration(t *testing.T) { } // Next response should be cached - cachedRespNextRound, err := app.Test(httptest.NewRequest("GET", "/", nil)) + cachedRespNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) cachedBodyNextRound, err := io.ReadAll(cachedRespNextRound.Body) require.NoError(t, err) @@ -560,12 +561,12 @@ func Test_AdditionalE2EResponseHeaders(t *testing.T) { return c.SendString("hi") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, "foobar", resp.Header.Get("X-Foobar")) - req = httptest.NewRequest("GET", "/", nil) + req = httptest.NewRequest(fiber.MethodGet, "/", nil) resp, err = app.Test(req) require.NoError(t, err) require.Equal(t, "foobar", resp.Header.Get("X-Foobar")) @@ -595,19 +596,19 @@ func Test_CacheHeader(t *testing.T) { return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String()) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) - resp, err = app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, cacheHit, resp.Header.Get("X-Cache")) - resp, err = app.Test(httptest.NewRequest("POST", "/?cache=12345", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil)) require.NoError(t, err) require.Equal(t, cacheUnreachable, resp.Header.Get("X-Cache")) - errRespCached, err := app.Test(httptest.NewRequest("GET", "/error", nil)) + errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) require.NoError(t, err) require.Equal(t, cacheUnreachable, errRespCached.Header.Get("X-Cache")) } @@ -624,12 +625,12 @@ func Test_Cache_WithHead(t *testing.T) { } app.Route("/").Get(handler).Head(handler) - req := httptest.NewRequest("HEAD", "/", nil) + req := httptest.NewRequest(fiber.MethodHead, "/", nil) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, cacheMiss, resp.Header.Get("X-Cache")) - cachedReq := httptest.NewRequest("HEAD", "/", nil) + cachedReq := httptest.NewRequest(fiber.MethodHead, "/", nil) cachedResp, err := app.Test(cachedReq) require.NoError(t, err) require.Equal(t, cacheHit, cachedResp.Header.Get("X-Cache")) @@ -653,28 +654,28 @@ func Test_Cache_WithHeadThenGet(t *testing.T) { } app.Route("/").Get(handler).Head(handler) - headResp, err := app.Test(httptest.NewRequest("HEAD", "/?cache=123", nil)) + headResp, err := app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil)) require.NoError(t, err) headBody, err := io.ReadAll(headResp.Body) 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)) + headResp, err = app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil)) require.NoError(t, err) headBody, err = io.ReadAll(headResp.Body) 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)) + getResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil)) require.NoError(t, err) getBody, err := io.ReadAll(getResp.Body) 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)) + getResp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil)) require.NoError(t, err) getBody, err = io.ReadAll(getResp.Body) require.NoError(t, err) @@ -695,7 +696,7 @@ func Test_CustomCacheHeader(t *testing.T) { return c.SendString("Hello, World!") }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, cacheMiss, resp.Header.Get("Cache-Status")) } @@ -706,7 +707,7 @@ func Test_CustomCacheHeader(t *testing.T) { func stableAscendingExpiration() func(c1 fiber.Ctx, c2 *Config) time.Duration { i := 0 return func(c1 fiber.Ctx, c2 *Config) time.Duration { - i += 1 + i++ return time.Hour * time.Duration(i) } } @@ -742,7 +743,7 @@ func Test_Cache_MaxBytesOrder(t *testing.T) { } for idx, tcase := range cases { - rsp, err := app.Test(httptest.NewRequest("GET", tcase[0], nil)) + rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil)) require.NoError(t, err) require.Equal(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) } @@ -760,7 +761,8 @@ func Test_Cache_MaxBytesSizes(t *testing.T) { app.Get("/*", func(c fiber.Ctx) error { path := c.Context().URI().LastPathSegment() - size, _ := strconv.Atoi(string(path)) + size, err := strconv.Atoi(string(path)) + require.NoError(t, err) return c.Send(make([]byte, size)) }) @@ -776,7 +778,7 @@ func Test_Cache_MaxBytesSizes(t *testing.T) { } for idx, tcase := range cases { - rsp, err := app.Test(httptest.NewRequest("GET", tcase[0], nil)) + rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil)) require.NoError(t, err) require.Equal(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) } @@ -789,14 +791,14 @@ func Benchmark_Cache(b *testing.B) { app.Use(New()) app.Get("/demo", func(c fiber.Ctx) error { - data, _ := os.ReadFile("../../.github/README.md") + data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark return c.Status(fiber.StatusTeapot).Send(data) }) h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/demo") b.ReportAllocs() @@ -819,14 +821,14 @@ func Benchmark_Cache_Storage(b *testing.B) { })) app.Get("/demo", func(c fiber.Ctx) error { - data, _ := os.ReadFile("../../.github/README.md") + data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark return c.Status(fiber.StatusTeapot).Send(data) }) h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/demo") b.ReportAllocs() @@ -854,7 +856,7 @@ func Benchmark_Cache_AdditionalHeaders(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/demo") b.ReportAllocs() @@ -886,7 +888,7 @@ func Benchmark_Cache_MaxSize(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) b.ReportAllocs() b.ResetTimer() diff --git a/middleware/cache/heap.go b/middleware/cache/heap.go index bcc279c2c5..fa97871595 100644 --- a/middleware/cache/heap.go +++ b/middleware/cache/heap.go @@ -41,7 +41,7 @@ func (h indexedHeap) Swap(i, j int) { } func (h *indexedHeap) Push(x any) { - h.pushInternal(x.(heapEntry)) + h.pushInternal(x.(heapEntry)) //nolint:forcetypeassert // Forced type assertion required to implement the heap.Interface interface } func (h *indexedHeap) Pop() any { @@ -65,7 +65,7 @@ func (h *indexedHeap) put(key string, exp uint64, bytes uint) int { idx = h.entries[:n+1][n].idx } else { idx = h.maxidx - h.maxidx += 1 + h.maxidx++ h.indices = append(h.indices, idx) } // Push manually to avoid allocation @@ -77,7 +77,7 @@ func (h *indexedHeap) put(key string, exp uint64, bytes uint) int { } func (h *indexedHeap) removeInternal(realIdx int) (string, uint) { - x := heap.Remove(h, realIdx).(heapEntry) + x := heap.Remove(h, realIdx).(heapEntry) //nolint:forcetypeassert,errcheck // Forced type assertion required to implement the heap.Interface interface return x.key, x.bytes } diff --git a/middleware/cache/manager.go b/middleware/cache/manager.go index 617e0d8991..c6ae542805 100644 --- a/middleware/cache/manager.go +++ b/middleware/cache/manager.go @@ -8,7 +8,8 @@ import ( "github.com/gofiber/fiber/v3/internal/memory" ) -//go:generate msgp -o=manager_msgp.go -io=false -unexported +// go:generate msgp +// msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported type item struct { body []byte ctype []byte @@ -48,7 +49,7 @@ func newManager(storage fiber.Storage) *manager { // acquire returns an *entry from the sync.Pool func (m *manager) acquire() *item { - return m.pool.Get().(*item) + return m.pool.Get().(*item) //nolint:forcetypeassert // We store nothing else in the pool } // release and reset *entry to sync.Pool @@ -66,38 +67,47 @@ func (m *manager) release(e *item) { } // get data from storage or memory -func (m *manager) get(key string) (it *item) { +func (m *manager) get(key string) *item { + var it *item if m.storage != nil { it = m.acquire() - if raw, _ := m.storage.Get(key); raw != nil { + raw, err := m.storage.Get(key) + if err != nil { + return it + } + if raw != nil { if _, err := it.UnmarshalMsg(raw); err != nil { - return + return it } } - return + return it } - if it, _ = m.memory.Get(key).(*item); it == nil { + if it, _ = m.memory.Get(key).(*item); it == nil { //nolint:errcheck // We store nothing else in the pool it = m.acquire() + return it } - return + return it } // get raw data from storage or memory -func (m *manager) getRaw(key string) (raw []byte) { +func (m *manager) getRaw(key string) []byte { + var raw []byte if m.storage != nil { - raw, _ = m.storage.Get(key) + raw, _ = m.storage.Get(key) //nolint:errcheck // TODO: Handle error here } else { - raw, _ = m.memory.Get(key).([]byte) + raw, _ = m.memory.Get(key).([]byte) //nolint:errcheck // TODO: Handle error here } - return + return raw } // set data to storage or memory func (m *manager) set(key string, it *item, exp time.Duration) { if m.storage != nil { if raw, err := it.MarshalMsg(nil); err == nil { - _ = m.storage.Set(key, raw, exp) + _ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Handle error here } + // we can release data because it's serialized to database + m.release(it) } else { m.memory.Set(key, it, exp) } @@ -106,16 +116,16 @@ func (m *manager) set(key string, it *item, exp time.Duration) { // set data to storage or memory func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { if m.storage != nil { - _ = m.storage.Set(key, raw, exp) + _ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Handle error here } else { m.memory.Set(key, raw, exp) } } // delete data from storage or memory -func (m *manager) delete(key string) { +func (m *manager) del(key string) { if m.storage != nil { - _ = m.storage.Delete(key) + _ = m.storage.Delete(key) //nolint:errcheck // TODO: Handle error here } else { m.memory.Delete(key) } diff --git a/middleware/compress/compress_test.go b/middleware/compress/compress_test.go index ced70df3c3..a4c3cd38b0 100644 --- a/middleware/compress/compress_test.go +++ b/middleware/compress/compress_test.go @@ -25,6 +25,7 @@ func init() { // go test -run Test_Compress_Gzip func Test_Compress_Gzip(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -34,7 +35,7 @@ func Test_Compress_Gzip(t *testing.T) { return c.Send(filedata) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) @@ -50,9 +51,11 @@ func Test_Compress_Gzip(t *testing.T) { // go test -run Test_Compress_Different_Level func Test_Compress_Different_Level(t *testing.T) { + t.Parallel() levels := []Level{LevelBestSpeed, LevelBestCompression} for _, level := range levels { t.Run(fmt.Sprintf("level %d", level), func(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Level: level})) @@ -62,7 +65,7 @@ func Test_Compress_Different_Level(t *testing.T) { return c.Send(filedata) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) @@ -79,6 +82,7 @@ func Test_Compress_Different_Level(t *testing.T) { } func Test_Compress_Deflate(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -87,7 +91,7 @@ func Test_Compress_Deflate(t *testing.T) { return c.Send(filedata) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "deflate") resp, err := app.Test(req) @@ -102,6 +106,7 @@ func Test_Compress_Deflate(t *testing.T) { } func Test_Compress_Brotli(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -110,7 +115,7 @@ func Test_Compress_Brotli(t *testing.T) { return c.Send(filedata) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "br") resp, err := app.Test(req, 10*time.Second) @@ -125,6 +130,7 @@ func Test_Compress_Brotli(t *testing.T) { } func Test_Compress_Disabled(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Level: LevelDisabled})) @@ -133,7 +139,7 @@ func Test_Compress_Disabled(t *testing.T) { return c.Send(filedata) }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "br") resp, err := app.Test(req) @@ -148,6 +154,7 @@ func Test_Compress_Disabled(t *testing.T) { } func Test_Compress_Next_Error(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -156,7 +163,7 @@ func Test_Compress_Next_Error(t *testing.T) { return errors.New("next error") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set("Accept-Encoding", "gzip") resp, err := app.Test(req) @@ -171,6 +178,7 @@ func Test_Compress_Next_Error(t *testing.T) { // go test -run Test_Compress_Next func Test_Compress_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -178,7 +186,7 @@ func Test_Compress_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 0356f38d0c..0239223d24 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -1,7 +1,6 @@ package cors import ( - "net/http" "strconv" "strings" @@ -128,7 +127,7 @@ func New(config ...Config) fiber.Handler { } // Simple request - if c.Method() != http.MethodOptions { + if c.Method() != fiber.MethodOptions { c.Vary(fiber.HeaderOrigin) c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 84e7bd9b73..bc508a1186 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -10,6 +10,7 @@ import ( ) func Test_CORS_Defaults(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -17,6 +18,7 @@ func Test_CORS_Defaults(t *testing.T) { } func Test_CORS_Empty_Config(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{})) @@ -49,6 +51,7 @@ func testDefaultOrEmptyConfig(t *testing.T, app *fiber.App) { // go test -run -v Test_CORS_Wildcard func Test_CORS_Wildcard(t *testing.T) { + t.Parallel() // New fiber instance app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is * @@ -88,6 +91,7 @@ func Test_CORS_Wildcard(t *testing.T) { // go test -run -v Test_CORS_Subdomain func Test_CORS_Subdomain(t *testing.T) { + t.Parallel() // New fiber instance app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is set to a subdomain @@ -122,6 +126,7 @@ func Test_CORS_Subdomain(t *testing.T) { } func Test_CORS_AllowOriginScheme(t *testing.T) { + t.Parallel() tests := []struct { reqOrigin, pattern string shouldAllowOrigin bool @@ -224,6 +229,7 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { // go test -run Test_CORS_Next func Test_CORS_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -231,7 +237,7 @@ func Test_CORS_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } diff --git a/middleware/cors/utils.go b/middleware/cors/utils.go index fee658ef93..8b6114bdab 100644 --- a/middleware/cors/utils.go +++ b/middleware/cors/utils.go @@ -1,6 +1,8 @@ package cors -import "strings" +import ( + "strings" +) func matchScheme(domain, pattern string) bool { didx := strings.Index(domain, ":") @@ -20,18 +22,20 @@ func matchSubdomain(domain, pattern string) bool { } domAuth := domain[didx+3:] // to avoid long loop by invalid long domain - if len(domAuth) > 253 { + const maxDomainLen = 253 + if len(domAuth) > maxDomainLen { return false } patAuth := pattern[pidx+3:] domComp := strings.Split(domAuth, ".") patComp := strings.Split(patAuth, ".") - for i := len(domComp)/2 - 1; i >= 0; i-- { + const divHalf = 2 + for i := len(domComp)/divHalf - 1; i >= 0; i-- { opp := len(domComp) - 1 - i domComp[i], domComp[opp] = domComp[opp], domComp[i] } - for i := len(patComp)/2 - 1; i >= 0; i-- { + for i := len(patComp)/divHalf - 1; i >= 0; i-- { opp := len(patComp) - 1 - i patComp[i], patComp[opp] = patComp[opp], patComp[i] } diff --git a/middleware/csrf/config.go b/middleware/csrf/config.go index 8c01c6c46f..c512082cbf 100644 --- a/middleware/csrf/config.go +++ b/middleware/csrf/config.go @@ -106,7 +106,7 @@ var ConfigDefault = Config{ } // default ErrorHandler that process return error from fiber.Handler -var defaultErrorHandler = func(c fiber.Ctx, err error) error { +func defaultErrorHandler(_ fiber.Ctx, _ error) error { return fiber.ErrForbidden } @@ -143,7 +143,8 @@ func configDefault(config ...Config) Config { // Generate the correct extractor to get the token from the correct location selectors := strings.Split(cfg.KeyLookup, ":") - if len(selectors) != 2 { + const numParts = 2 + if len(selectors) != numParts { panic("[CSRF] KeyLookup must in the form of :") } diff --git a/middleware/csrf/csrf.go b/middleware/csrf/csrf.go index 29512daba9..bd6c32cac8 100644 --- a/middleware/csrf/csrf.go +++ b/middleware/csrf/csrf.go @@ -7,9 +7,7 @@ import ( "github.com/gofiber/fiber/v3" ) -var ( - errTokenNotFound = errors.New("csrf token not found") -) +var errTokenNotFound = errors.New("csrf token not found") // New creates a new middleware handler func New(config ...Config) fiber.Handler { @@ -22,7 +20,7 @@ func New(config ...Config) fiber.Handler { dummyValue := []byte{'+'} // Return new handler - return func(c fiber.Ctx) (err error) { + return func(c fiber.Ctx) error { // Don't execute middleware if Next returns true if cfg.Next != nil && cfg.Next(c) { return c.Next() @@ -39,7 +37,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 45049da8df..24c3b97dea 100644 --- a/middleware/csrf/csrf_test.go +++ b/middleware/csrf/csrf_test.go @@ -12,6 +12,7 @@ import ( ) func Test_CSRF(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -23,7 +24,7 @@ func Test_CSRF(t *testing.T) { h := app.Handler() ctx := &fasthttp.RequestCtx{} - methods := [4]string{"GET", "HEAD", "OPTIONS", "TRACE"} + methods := [4]string{fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace} for _, method := range methods { // Generate CSRF token @@ -33,14 +34,14 @@ func Test_CSRF(t *testing.T) { // Without CSRF cookie ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) 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.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(HeaderName, "johndoe") h(ctx) require.Equal(t, 403, ctx.Response.StatusCode()) @@ -55,7 +56,7 @@ func Test_CSRF(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(HeaderName, token) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) @@ -64,6 +65,7 @@ func Test_CSRF(t *testing.T) { // go test -run Test_CSRF_Next func Test_CSRF_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -71,12 +73,13 @@ func Test_CSRF_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_CSRF_Invalid_KeyLookup(t *testing.T) { + t.Parallel() defer func() { require.Equal(t, "[CSRF] KeyLookup must in the form of :", recover()) }() @@ -90,11 +93,12 @@ func Test_CSRF_Invalid_KeyLookup(t *testing.T) { h := app.Handler() ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) } func Test_CSRF_From_Form(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{KeyLookup: "form:_csrf"})) @@ -107,7 +111,7 @@ func Test_CSRF_From_Form(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Invalid CSRF token - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm) h(ctx) require.Equal(t, 403, ctx.Response.StatusCode()) @@ -115,12 +119,12 @@ func Test_CSRF_From_Form(t *testing.T) { // Generate CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) 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.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm) ctx.Request.SetBodyString("_csrf=" + token) h(ctx) @@ -128,6 +132,7 @@ func Test_CSRF_From_Form(t *testing.T) { } func Test_CSRF_From_Query(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{KeyLookup: "query:_csrf"})) @@ -140,7 +145,7 @@ func Test_CSRF_From_Query(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Invalid CSRF token - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.SetRequestURI("/?_csrf=" + utils.UUID()) h(ctx) require.Equal(t, 403, ctx.Response.StatusCode()) @@ -148,7 +153,7 @@ func Test_CSRF_From_Query(t *testing.T) { // Generate CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) ctx.Request.SetRequestURI("/") h(ctx) token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) @@ -157,13 +162,14 @@ func Test_CSRF_From_Query(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() ctx.Request.SetRequestURI("/?_csrf=" + token) - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) require.Equal(t, "OK", string(ctx.Response.Body())) } func Test_CSRF_From_Param(t *testing.T) { + t.Parallel() app := fiber.New() csrfGroup := app.Group("/:csrf", New(Config{KeyLookup: "param:csrf"})) @@ -176,7 +182,7 @@ func Test_CSRF_From_Param(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Invalid CSRF token - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.SetRequestURI("/" + utils.UUID()) h(ctx) require.Equal(t, 403, ctx.Response.StatusCode()) @@ -184,7 +190,7 @@ func Test_CSRF_From_Param(t *testing.T) { // Generate CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) ctx.Request.SetRequestURI("/" + utils.UUID()) h(ctx) token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) @@ -193,13 +199,14 @@ func Test_CSRF_From_Param(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() ctx.Request.SetRequestURI("/" + token) - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) require.Equal(t, "OK", string(ctx.Response.Body())) } func Test_CSRF_From_Cookie(t *testing.T) { + t.Parallel() app := fiber.New() csrfGroup := app.Group("/", New(Config{KeyLookup: "cookie:csrf"})) @@ -212,7 +219,7 @@ func Test_CSRF_From_Cookie(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Invalid CSRF token - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.SetRequestURI("/") ctx.Request.Header.Set(fiber.HeaderCookie, "csrf="+utils.UUID()+";") h(ctx) @@ -221,7 +228,7 @@ func Test_CSRF_From_Cookie(t *testing.T) { // Generate CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) ctx.Request.SetRequestURI("/") h(ctx) token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) @@ -229,7 +236,7 @@ func Test_CSRF_From_Cookie(t *testing.T) { ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(fiber.HeaderCookie, "csrf="+token+";") ctx.Request.SetRequestURI("/") h(ctx) @@ -238,6 +245,7 @@ func Test_CSRF_From_Cookie(t *testing.T) { } func Test_CSRF_From_Custom(t *testing.T) { + t.Parallel() app := fiber.New() extractor := func(c fiber.Ctx) (string, error) { @@ -261,7 +269,7 @@ func Test_CSRF_From_Custom(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Invalid CSRF token - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMETextPlain) h(ctx) require.Equal(t, 403, ctx.Response.StatusCode()) @@ -269,12 +277,12 @@ func Test_CSRF_From_Custom(t *testing.T) { // Generate CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) 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.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMETextPlain) ctx.Request.SetBodyString("_csrf=" + token) h(ctx) @@ -282,6 +290,7 @@ func Test_CSRF_From_Custom(t *testing.T) { } func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) { + t.Parallel() app := fiber.New() errHandler := func(ctx fiber.Ctx, err error) error { @@ -299,13 +308,13 @@ func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Generate CSRF token - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) // invalid CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(HeaderName, "johndoe") h(ctx) require.Equal(t, 419, ctx.Response.StatusCode()) @@ -313,6 +322,7 @@ func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) { } func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) { + t.Parallel() app := fiber.New() errHandler := func(ctx fiber.Ctx, err error) error { @@ -330,68 +340,69 @@ func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) { ctx := &fasthttp.RequestCtx{} // Generate CSRF token - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) // empty CSRF token ctx.Request.Reset() ctx.Response.Reset() - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) 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) -//} +// func Test_CSRF_UnsafeHeaderValue(t *testing.T) { +// t.Parallel() +// app := fiber.New() + +// app.Use(New()) +// app.Get("/", func(c *fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) +// app.Get("/test", func(c *fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) +// app.Post("/", func(c *fiber.Ctx) error { +// return c.SendStatus(fiber.StatusOK) +// }) + +// resp, err := app.Test(httptest.NewRequest(fiber.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(fiber.MethodGet, "/", nil) +// getReq.Header.Set(HeaderName, token) +// resp, err = app.Test(getReq) + +// getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil) +// getReq.Header.Set("X-Requested-With", "XMLHttpRequest") +// getReq.Header.Set(fiber.HeaderCacheControl, "no") +// getReq.Header.Set(HeaderName, token) + +// resp, err = app.Test(getReq) + +// getReq.Header.Set(fiber.HeaderAccept, "*/*") +// getReq.Header.Del(HeaderName) +// resp, err = app.Test(getReq) + +// postReq := httptest.NewRequest(fiber.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) { @@ -407,12 +418,12 @@ func Benchmark_Middleware_CSRF_Check(b *testing.B) { ctx := &fasthttp.RequestCtx{} // Generate CSRF token - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) 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.SetMethod(fiber.MethodPost) ctx.Request.Header.Set(HeaderName, token) b.ReportAllocs() @@ -439,7 +450,7 @@ func Benchmark_Middleware_CSRF_GenerateToken(b *testing.B) { ctx := &fasthttp.RequestCtx{} // Generate CSRF token - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) b.ReportAllocs() b.ResetTimer() diff --git a/middleware/csrf/manager.go b/middleware/csrf/manager.go index a0f5b6aff8..e16e8d19fb 100644 --- a/middleware/csrf/manager.go +++ b/middleware/csrf/manager.go @@ -9,7 +9,8 @@ import ( "github.com/gofiber/utils/v2" ) -//go:generate msgp -o=manager_msgp.go -io=false -unexported +// go:generate msgp +// msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported type item struct{} //msgp:ignore manager @@ -38,74 +39,23 @@ func newManager(storage fiber.Storage) *manager { return manager } -// acquire returns an *entry from the sync.Pool -func (m *manager) acquire() *item { - return m.pool.Get().(*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) { - 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) { +func (m *manager) getRaw(key string) []byte { + var raw []byte if m.storage != nil { - raw, _ = m.storage.Get(key) + raw, _ = m.storage.Get(key) //nolint:errcheck // TODO: Do not ignore error } 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 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) + raw, _ = m.memory.Get(key).([]byte) //nolint:errcheck // TODO: Do not ignore error } + return raw } // set data to storage or memory func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { if m.storage != nil { - _ = m.storage.Set(key, raw, exp) + _ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Do not ignore error } 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) { - if m.storage != nil { - _ = m.storage.Delete(key) - } else { - m.memory.Delete(key) - } -} diff --git a/middleware/earlydata/earlydata_test.go b/middleware/earlydata/earlydata_test.go index aee22c61f2..619df60023 100644 --- a/middleware/earlydata/earlydata_test.go +++ b/middleware/earlydata/earlydata_test.go @@ -1,3 +1,4 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package earlydata_test import ( diff --git a/middleware/encryptcookie/README.md b/middleware/encryptcookie/README.md index 80bdfff8c2..283c8c60d6 100644 --- a/middleware/encryptcookie/README.md +++ b/middleware/encryptcookie/README.md @@ -69,8 +69,8 @@ type Config struct { // Base64 encoded unique key to encode & decode cookies. // - // Required. Key length should be 32 characters. - // You may use `encryptcookie.GenerateKey()` to generate a new key. + // Required. The key should be 32 bytes of random data in base64-encoded form. + // You may run `openssl rand -base64 32` or use `encryptcookie.GenerateKey()` to generate a new key. Key string // Custom function to encrypt cookies. @@ -89,7 +89,7 @@ type Config struct { ```go // `Key` must be a 32 character string. It's used to encrpyt the values, so make sure it is random and keep it secret. -// You can call `encryptcookie.GenerateKey()` to create a random key for you. +// You can run `openssl rand -base64 32` or call `encryptcookie.GenerateKey()` to create a random key for you. // Make sure not to set `Key` to `encryptcookie.GenerateKey()` because that will create a new key every run. app.Use(encryptcookie.New(encryptcookie.Config{ Key: "secret-thirty-2-character-string", @@ -110,4 +110,4 @@ app.Use(csrf.New(csrf.Config{ CookieName: "csrf_1", CookieHTTPOnly: true, })) -``` \ No newline at end of file +``` diff --git a/middleware/encryptcookie/encryptcookie.go b/middleware/encryptcookie/encryptcookie.go index 0bbc892a10..a8ce216be2 100644 --- a/middleware/encryptcookie/encryptcookie.go +++ b/middleware/encryptcookie/encryptcookie.go @@ -41,12 +41,12 @@ func New(config ...Config) fiber.Handler { cookieValue.SetKeyBytes(key) if c.Response().Header.Cookie(&cookieValue) { encryptedValue, err := cfg.Encryptor(string(cookieValue.Value()), cfg.Key) - if err == nil { - cookieValue.SetValue(encryptedValue) - c.Response().Header.SetCookie(&cookieValue) - } else { + if err != nil { panic(err) } + + cookieValue.SetValue(encryptedValue) + c.Response().Header.SetCookie(&cookieValue) } } }) diff --git a/middleware/encryptcookie/encryptcookie_test.go b/middleware/encryptcookie/encryptcookie_test.go index c4d400e94c..ee7ca9ae60 100644 --- a/middleware/encryptcookie/encryptcookie_test.go +++ b/middleware/encryptcookie/encryptcookie_test.go @@ -13,6 +13,7 @@ import ( var testKey = GenerateKey() func Test_Middleware_Encrypt_Cookie(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -34,14 +35,14 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) { // Test empty cookie ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) 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.SetMethod(fiber.MethodGet) ctx.Request.Header.SetCookie("test", "Invalid") h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) @@ -53,18 +54,19 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) { // Test valid cookie ctx = &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test") require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") - decryptedCookieValue, _ := DecryptCookie(string(encryptedCookie.Value()), testKey) + decryptedCookieValue, err := DecryptCookie(string(encryptedCookie.Value()), testKey) + require.NoError(t, err) require.Equal(t, "SomeThing", decryptedCookieValue) ctx = &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value())) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) @@ -72,6 +74,7 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) { } func Test_Encrypt_Cookie_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -89,12 +92,13 @@ func Test_Encrypt_Cookie_Next(t *testing.T) { return nil }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, "SomeThing", resp.Cookies()[0].Value) } func Test_Encrypt_Cookie_Except(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -120,7 +124,7 @@ func Test_Encrypt_Cookie_Except(t *testing.T) { h := app.Handler() ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) @@ -132,11 +136,13 @@ func Test_Encrypt_Cookie_Except(t *testing.T) { encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test2") require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") - decryptedCookieValue, _ := DecryptCookie(string(encryptedCookie.Value()), testKey) + decryptedCookieValue, err := DecryptCookie(string(encryptedCookie.Value()), testKey) + require.NoError(t, err) require.Equal(t, "SomeThing", decryptedCookieValue) } func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -165,18 +171,19 @@ func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) { h := app.Handler() ctx := &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("POST") + ctx.Request.Header.SetMethod(fiber.MethodPost) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) encryptedCookie := fasthttp.Cookie{} encryptedCookie.SetKey("test") require.True(t, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value") - decodedBytes, _ := base64.StdEncoding.DecodeString(string(encryptedCookie.Value())) + decodedBytes, err := base64.StdEncoding.DecodeString(string(encryptedCookie.Value())) + require.NoError(t, err) require.Equal(t, "SomeThing", string(decodedBytes)) ctx = &fasthttp.RequestCtx{} - ctx.Request.Header.SetMethod("GET") + ctx.Request.Header.SetMethod(fiber.MethodGet) ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value())) h(ctx) require.Equal(t, 200, ctx.Response.StatusCode()) diff --git a/middleware/encryptcookie/utils.go b/middleware/encryptcookie/utils.go index 542d160c17..c35064d954 100644 --- a/middleware/encryptcookie/utils.go +++ b/middleware/encryptcookie/utils.go @@ -6,47 +6,56 @@ import ( "crypto/rand" "encoding/base64" "errors" + "fmt" "io" ) // EncryptCookie Encrypts a cookie value with specific encryption key func EncryptCookie(value, key string) (string, error) { - keyDecoded, _ := base64.StdEncoding.DecodeString(key) - plaintext := []byte(value) + keyDecoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("failed to base64-decode key: %w", err) + } block, err := aes.NewCipher(keyDecoded) if err != nil { - return "", err + return "", fmt.Errorf("failed to create AES cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { - return "", err + return "", fmt.Errorf("failed to create GCM mode: %w", err) } nonce := make([]byte, gcm.NonceSize()) if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return "", err + return "", fmt.Errorf("failed to read: %w", err) } - ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) + ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil) return base64.StdEncoding.EncodeToString(ciphertext), nil } // DecryptCookie Decrypts a cookie value with specific encryption key func DecryptCookie(value, key string) (string, error) { - keyDecoded, _ := base64.StdEncoding.DecodeString(key) - enc, _ := base64.StdEncoding.DecodeString(value) + keyDecoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("failed to base64-decode key: %w", err) + } + enc, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return "", fmt.Errorf("failed to base64-decode value: %w", err) + } block, err := aes.NewCipher(keyDecoded) if err != nil { - return "", err + return "", fmt.Errorf("failed to create AES cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { - return "", err + return "", fmt.Errorf("failed to create GCM mode: %w", err) } nonceSize := gcm.NonceSize() @@ -59,7 +68,7 @@ func DecryptCookie(value, key string) (string, error) { plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { - return "", err + return "", fmt.Errorf("failed to decrypt ciphertext: %w", err) } return string(plaintext), nil @@ -67,7 +76,8 @@ func DecryptCookie(value, key string) (string, error) { // GenerateKey Generates an encryption key func GenerateKey() string { - ret := make([]byte, 32) + const keyLen = 32 + ret := make([]byte, keyLen) if _, err := rand.Read(ret); err != nil { panic(err) diff --git a/middleware/envvar/envvar.go b/middleware/envvar/envvar.go index 47cd93f1c1..4b9aaf802a 100644 --- a/middleware/envvar/envvar.go +++ b/middleware/envvar/envvar.go @@ -23,10 +23,8 @@ func (envVar *EnvVar) set(key, val string) { envVar.Vars[key] = val } -var defaultConfig = Config{} - func New(config ...Config) fiber.Handler { - var cfg = defaultConfig + var cfg Config if len(config) > 0 { cfg = config[0] } @@ -57,8 +55,9 @@ func newEnvVar(cfg Config) *EnvVar { } } } else { + const numElems = 2 for _, envVal := range os.Environ() { - keyVal := strings.SplitN(envVal, "=", 2) + keyVal := strings.SplitN(envVal, "=", numElems) if _, exists := cfg.ExcludeVars[keyVal[0]]; !exists { vars.set(keyVal[0], keyVal[1]) } diff --git a/middleware/envvar/envvar_test.go b/middleware/envvar/envvar_test.go index 527431d2e0..880b727e5a 100644 --- a/middleware/envvar/envvar_test.go +++ b/middleware/envvar/envvar_test.go @@ -1,6 +1,8 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package envvar import ( + "context" "encoding/json" "io" "net/http" @@ -17,7 +19,8 @@ func TestEnvVarStructWithExportVarsExcludeVars(t *testing.T) { vars := newEnvVar(Config{ ExportVars: map[string]string{"testKey": "", "testDefaultKey": "testDefaultVal"}, - ExcludeVars: map[string]string{"excludeKey": ""}}) + ExcludeVars: map[string]string{"excludeKey": ""}, + }) require.Equal(t, vars.Vars["testKey"], "testEnvValue") require.Equal(t, vars.Vars["testDefaultKey"], "testDefaultVal") @@ -28,18 +31,21 @@ func TestEnvVarStructWithExportVarsExcludeVars(t *testing.T) { func TestEnvVarHandler(t *testing.T) { t.Setenv("testKey", "testVal") - expectedEnvVarResponse, _ := json.Marshal( + expectedEnvVarResponse, err := json.Marshal( struct { Vars map[string]string `json:"vars"` }{ map[string]string{"testKey": "testVal"}, }) + require.NoError(t, err) app := fiber.New() app.Use("/envvars", New(Config{ - ExportVars: map[string]string{"testKey": ""}})) + ExportVars: map[string]string{"testKey": ""}, + })) - req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, "http://localhost/envvars", nil) + require.NoError(t, err) resp, err := app.Test(req) require.Equal(t, nil, err) @@ -52,14 +58,16 @@ func TestEnvVarHandler(t *testing.T) { func TestEnvVarHandlerNotMatched(t *testing.T) { app := fiber.New() app.Use("/envvars", New(Config{ - ExportVars: map[string]string{"testKey": ""}})) + 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) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, "http://localhost/another-path", nil) + require.NoError(t, err) resp, err := app.Test(req) require.Equal(t, nil, err) @@ -75,7 +83,8 @@ func TestEnvVarHandlerDefaultConfig(t *testing.T) { app := fiber.New() app.Use("/envvars", New()) - req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, "http://localhost/envvars", nil) + require.NoError(t, err) resp, err := app.Test(req) require.Equal(t, nil, err) @@ -92,7 +101,8 @@ func TestEnvVarHandlerMethod(t *testing.T) { app := fiber.New() app.Use("/envvars", New()) - req, _ := http.NewRequest("POST", "http://localhost/envvars", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodPost, "http://localhost/envvars", nil) + require.NoError(t, err) resp, err := app.Test(req) require.Equal(t, nil, err) require.Equal(t, fiber.StatusMethodNotAllowed, resp.StatusCode) @@ -107,19 +117,21 @@ func TestEnvVarHandlerSpecialValue(t *testing.T) { app.Use("/envvars", New()) app.Use("/envvars/export", New(Config{ExportVars: map[string]string{testEnvKey: ""}})) - req, _ := http.NewRequest("GET", "http://localhost/envvars", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, "http://localhost/envvars", nil) + require.NoError(t, err) resp, err := app.Test(req) - require.Equal(t, nil, err) + require.NoError(t, err) respBody, err := io.ReadAll(resp.Body) - require.Equal(t, nil, err) + require.NoError(t, 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) + req, err = http.NewRequestWithContext(context.Background(), fiber.MethodGet, "http://localhost/envvars/export", nil) + require.NoError(t, err) resp, err = app.Test(req) require.Equal(t, nil, err) diff --git a/middleware/etag/etag.go b/middleware/etag/etag.go index fe94fc9337..38fdc0165d 100644 --- a/middleware/etag/etag.go +++ b/middleware/etag/etag.go @@ -8,17 +8,18 @@ import ( "github.com/valyala/bytebufferpool" ) -var ( - normalizedHeaderETag = []byte("Etag") - weakPrefix = []byte("W/") -) - // New creates a new middleware handler func New(config ...Config) fiber.Handler { // Set default config cfg := configDefault(config...) - crc32q := crc32.MakeTable(0xD5828281) + var ( + normalizedHeaderETag = []byte("Etag") + weakPrefix = []byte("W/") + ) + + const crcPol = 0xD5828281 + crc32q := crc32.MakeTable(crcPol) // Return new handler return func(c fiber.Ctx) (err error) { @@ -28,22 +29,22 @@ func New(config ...Config) fiber.Handler { } // Return err if next handler returns one - if err = c.Next(); err != nil { - return + if err := c.Next(); err != nil { + return err } // Don't generate ETags for invalid responses if c.Response().StatusCode() != fiber.StatusOK { - return + return nil } body := c.Response().Body() // Skips ETag if no response body is present if len(body) == 0 { - return + return nil } // Skip ETag if header is already present if c.Response().Header.PeekBytes(normalizedHeaderETag) != nil { - return + return nil } // Generate ETag for response @@ -52,14 +53,14 @@ func New(config ...Config) fiber.Handler { // Enable weak tag if cfg.Weak { - _, _ = bb.Write(weakPrefix) + _, _ = bb.Write(weakPrefix) //nolint:errcheck // This will never fail } - _ = bb.WriteByte('"') + _ = bb.WriteByte('"') //nolint:errcheck // This will never fail bb.B = appendUint(bb.Bytes(), uint32(len(body))) - _ = bb.WriteByte('-') + _ = bb.WriteByte('-') //nolint:errcheck // This will never fail bb.B = appendUint(bb.Bytes(), crc32.Checksum(body, crc32q)) - _ = bb.WriteByte('"') + _ = bb.WriteByte('"') //nolint:errcheck // This will never fail etag := bb.Bytes() @@ -78,7 +79,7 @@ func New(config ...Config) fiber.Handler { // W/1 != W/2 || W/1 != 2 c.Response().Header.SetCanonical(normalizedHeaderETag, etag) - return + return nil } if bytes.Contains(clientEtag, etag) { @@ -90,7 +91,7 @@ func New(config ...Config) fiber.Handler { // 1 != 2 c.Response().Header.SetCanonical(normalizedHeaderETag, etag) - return + return nil } } diff --git a/middleware/etag/etag_test.go b/middleware/etag/etag_test.go index 4768eb5793..19394534e1 100644 --- a/middleware/etag/etag_test.go +++ b/middleware/etag/etag_test.go @@ -13,6 +13,7 @@ import ( // go test -run Test_ETag_Next func Test_ETag_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -20,13 +21,14 @@ func Test_ETag_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_ETag_SkipError func Test_ETag_SkipError(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -35,13 +37,14 @@ func Test_ETag_SkipError(t *testing.T) { return fiber.ErrForbidden }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusForbidden, resp.StatusCode) } // go test -run Test_ETag_NotStatusOK func Test_ETag_NotStatusOK(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -50,13 +53,14 @@ func Test_ETag_NotStatusOK(t *testing.T) { return c.SendStatus(fiber.StatusCreated) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusCreated, resp.StatusCode) } // go test -run Test_ETag_NoBody func Test_ETag_NoBody(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -65,25 +69,29 @@ func Test_ETag_NoBody(t *testing.T) { return nil }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) } // go test -run Test_ETag_NewEtag func Test_ETag_NewEtag(t *testing.T) { + t.Parallel() t.Run("without HeaderIfNoneMatch", func(t *testing.T) { + t.Parallel() testETagNewEtag(t, false, false) }) t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) { + t.Parallel() testETagNewEtag(t, true, false) }) t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) { + t.Parallel() testETagNewEtag(t, true, true) }) } -func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { +func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine t.Helper() app := fiber.New() @@ -94,7 +102,7 @@ func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { return c.SendString("Hello, World!") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) if headerIfNoneMatch { etag := `"non-match"` if matched { @@ -122,18 +130,22 @@ func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { // go test -run Test_ETag_WeakEtag func Test_ETag_WeakEtag(t *testing.T) { + t.Parallel() t.Run("without HeaderIfNoneMatch", func(t *testing.T) { + t.Parallel() testETagWeakEtag(t, false, false) }) t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) { + t.Parallel() testETagWeakEtag(t, true, false) }) t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) { + t.Parallel() testETagWeakEtag(t, true, true) }) } -func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { +func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine t.Helper() app := fiber.New() @@ -144,7 +156,7 @@ func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { return c.SendString("Hello, World!") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) if headerIfNoneMatch { etag := `W/"non-match"` if matched { @@ -172,18 +184,22 @@ func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { // go test -run Test_ETag_CustomEtag func Test_ETag_CustomEtag(t *testing.T) { + t.Parallel() t.Run("without HeaderIfNoneMatch", func(t *testing.T) { + t.Parallel() testETagCustomEtag(t, false, false) }) t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) { + t.Parallel() testETagCustomEtag(t, true, false) }) t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) { + t.Parallel() testETagCustomEtag(t, true, true) }) } -func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { +func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine t.Helper() app := fiber.New() @@ -198,7 +214,7 @@ func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { return c.SendString("Hello, World!") }) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) if headerIfNoneMatch { etag := `"non-match"` if matched { @@ -226,6 +242,7 @@ func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { // go test -run Test_ETag_CustomEtagPut func Test_ETag_CustomEtagPut(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -238,7 +255,7 @@ func Test_ETag_CustomEtagPut(t *testing.T) { return c.SendString("Hello, World!") }) - req := httptest.NewRequest("PUT", "/", nil) + req := httptest.NewRequest(fiber.MethodPut, "/", nil) req.Header.Set(fiber.HeaderIfMatch, `"non-match"`) resp, err := app.Test(req) require.NoError(t, err) @@ -258,7 +275,7 @@ func Benchmark_Etag(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") b.ReportAllocs() diff --git a/middleware/expvar/expvar_test.go b/middleware/expvar/expvar_test.go index 98e1a48560..a55198255f 100644 --- a/middleware/expvar/expvar_test.go +++ b/middleware/expvar/expvar_test.go @@ -11,6 +11,7 @@ import ( ) func Test_Non_Expvar_Path(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -29,6 +30,7 @@ func Test_Non_Expvar_Path(t *testing.T) { } func Test_Expvar_Index(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -49,6 +51,7 @@ func Test_Expvar_Index(t *testing.T) { } func Test_Expvar_Filter(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -69,6 +72,7 @@ func Test_Expvar_Filter(t *testing.T) { } func Test_Expvar_Other_Path(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -85,7 +89,6 @@ func Test_Expvar_Other_Path(t *testing.T) { // go test -run Test_Expvar_Next func Test_Expvar_Next(t *testing.T) { t.Parallel() - app := fiber.New() app.Use(New(Config{ diff --git a/middleware/favicon/README.md b/middleware/favicon/README.md index 8b74ba2b3b..531612d927 100644 --- a/middleware/favicon/README.md +++ b/middleware/favicon/README.md @@ -2,7 +2,7 @@ Favicon middleware for [Fiber](https://github.com/gofiber/fiber) that ignores favicon requests or caches a provided icon in memory to improve performance by skipping disk access. User agents request favicon.ico frequently and indiscriminately, so you may wish to exclude these requests from your logs by using this middleware before your logger middleware. -**Note** This middleware is exclusively for serving the default, implicit favicon, which is GET /favicon.ico. +**Note** This middleware is exclusively for serving the default, implicit favicon, which is GET /favicon.ico or [custom favicon URL](#config). ## Table of Contents - [Favicon Middleware](#favicon-middleware) @@ -13,6 +13,7 @@ Favicon middleware for [Fiber](https://github.com/gofiber/fiber) that ignores fa - [Custom Config](#custom-config) - [Config](#config) - [Default Config](#default-config-1) + ## Signatures ```go @@ -42,6 +43,7 @@ app.Use(favicon.New()) ```go app.Use(favicon.New(favicon.Config{ File: "./favicon.ico", + URL: "/favicon.ico", })) ``` @@ -59,6 +61,22 @@ type Config struct { // // Optional. Default: "" File string + + // URL for favicon handler + // + // Optional. Default: "/favicon.ico" + URL string + + // FileSystem is an optional alternate filesystem to search for the favicon in. + // An example of this could be an embedded or network filesystem + // + // Optional. Default: nil + FileSystem http.FileSystem + + // CacheControl defines how the Cache-Control header in the response should be set + // + // Optional. Default: "public, max-age=31536000" + CacheControl string } ``` @@ -67,6 +85,7 @@ type Config struct { ```go var ConfigDefault = Config{ Next: nil, - File: "" + File: "", + URL: "/favicon.ico", } ``` diff --git a/middleware/favicon/favicon.go b/middleware/favicon/favicon.go index ce5d042115..1e94ef983a 100644 --- a/middleware/favicon/favicon.go +++ b/middleware/favicon/favicon.go @@ -21,6 +21,11 @@ type Config struct { // Optional. Default: "" File string `json:"file"` + // URL for favicon handler + // + // Optional. Default: "/favicon.ico" + URL string `json:"url"` + // FileSystem is an optional alternate filesystem to search for the favicon in. // An example of this could be an embedded or network filesystem // @@ -37,6 +42,7 @@ type Config struct { var ConfigDefault = Config{ Next: nil, File: "", + URL: fPath, CacheControl: "public, max-age=31536000", } @@ -60,6 +66,9 @@ func New(config ...Config) fiber.Handler { if cfg.Next == nil { cfg.Next = ConfigDefault.Next } + if cfg.URL == "" { + cfg.URL = ConfigDefault.URL + } if cfg.File == "" { cfg.File = ConfigDefault.File } @@ -99,7 +108,7 @@ func New(config ...Config) fiber.Handler { } // Only respond to favicon requests - if c.Path() != fPath { + if c.Path() != cfg.URL { return c.Next() } diff --git a/middleware/favicon/favicon_test.go b/middleware/favicon/favicon_test.go index 3db323cd7e..918b4a3ffd 100644 --- a/middleware/favicon/favicon_test.go +++ b/middleware/favicon/favicon_test.go @@ -1,3 +1,4 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package favicon import ( @@ -13,6 +14,7 @@ import ( // go test -run Test_Middleware_Favicon func Test_Middleware_Favicon(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -22,19 +24,19 @@ func Test_Middleware_Favicon(t *testing.T) { }) // Skip Favicon middleware - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/favicon.ico", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodOptions, "/favicon.ico", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodPut, "/favicon.ico", nil)) 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)) @@ -42,6 +44,7 @@ func Test_Middleware_Favicon(t *testing.T) { // go test -run Test_Middleware_Favicon_Not_Found func Test_Middleware_Favicon_Not_Found(t *testing.T) { + t.Parallel() defer func() { if err := recover(); err == nil { t.Fatal("should cache panic") @@ -55,6 +58,7 @@ func Test_Middleware_Favicon_Not_Found(t *testing.T) { // go test -run Test_Middleware_Favicon_Found func Test_Middleware_Favicon_Found(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -65,7 +69,7 @@ func Test_Middleware_Favicon_Found(t *testing.T) { return nil }) - resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/favicon.ico", nil)) require.NoError(t, err, "app.Test(req)") require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") @@ -75,6 +79,7 @@ func Test_Middleware_Favicon_Found(t *testing.T) { // go test -run Test_Middleware_Favicon_FileSystem func Test_Middleware_Favicon_FileSystem(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -82,7 +87,7 @@ func Test_Middleware_Favicon_FileSystem(t *testing.T) { FileSystem: os.DirFS("../../.github/testdata"), })) - resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/favicon.ico", nil)) 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)) @@ -91,6 +96,7 @@ func Test_Middleware_Favicon_FileSystem(t *testing.T) { // go test -run Test_Middleware_Favicon_CacheControl func Test_Middleware_Favicon_CacheControl(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -98,7 +104,7 @@ func Test_Middleware_Favicon_CacheControl(t *testing.T) { File: "../../.github/testdata/favicon.ico", })) - resp, err := app.Test(httptest.NewRequest("GET", "/favicon.ico", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/favicon.ico", nil)) 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)) @@ -126,6 +132,7 @@ func Benchmark_Middleware_Favicon(b *testing.B) { // go test -run Test_Favicon_Next func Test_Favicon_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -133,7 +140,27 @@ func Test_Favicon_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } + +// go test -run Test_Custom_Favicon_URL +func Test_Custom_Favicon_URL(t *testing.T) { + app := fiber.New() + const customURL = "/favicon.svg" + app.Use(New(Config{ + File: "../../.github/testdata/favicon.ico", + URL: customURL, + })) + + app.Get("/", func(c fiber.Ctx) error { + return nil + }) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, customURL, nil)) + + 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)) +} diff --git a/middleware/filesystem/README.md b/middleware/filesystem/README.md index e7f3cbbb5e..18097fb7a8 100644 --- a/middleware/filesystem/README.md +++ b/middleware/filesystem/README.md @@ -2,7 +2,8 @@ Filesystem middleware for [Fiber](https://github.com/gofiber/fiber) that enables you to serve files from a directory. -⚠️ **`:params` & `:optionals?` within the prefix path are not supported!** +⚠️ **`:params` & `:optionals?` within the prefix path are not supported!** +⚠️ **To handle paths with spaces (or other url encoded values) make sure to set `fiber.Config{ UnescapePath: true}`** ## Table of Contents diff --git a/middleware/filesystem/filesystem.go b/middleware/filesystem/filesystem.go index c8f08b636f..cdbc38aad8 100644 --- a/middleware/filesystem/filesystem.go +++ b/middleware/filesystem/filesystem.go @@ -1,6 +1,7 @@ package filesystem import ( + "fmt" "io/fs" "net/http" "os" @@ -68,7 +69,11 @@ var ConfigDefault = Config{ MaxAge: 0, } -// New creates a new middleware handler +// New creates a new middleware handler. +// +// filesystem does not handle url encoded values (for example spaces) +// on it's own. If you need that functionality, set "UnescapePath" +// in fiber.Config func New(config ...Config) fiber.Handler { // Set default config cfg := ConfigDefault @@ -110,7 +115,7 @@ func New(config ...Config) fiber.Handler { cacheControlStr := "public, max-age=" + strconv.Itoa(cfg.MaxAge) // Return new handler - return func(c fiber.Ctx) (err error) { + return func(c fiber.Ctx) error { // Don't execute middleware if Next returns true if cfg.Next != nil && cfg.Next(c) { return c.Next() @@ -149,21 +154,21 @@ func New(config ...Config) fiber.Handler { path = strings.TrimRight(path, "/") } - file, err = openFile(cfg.Root, path) + file, err := openFile(cfg.Root, path) if err != nil && os.IsNotExist(err) && cfg.NotFoundFile != "" { file, err = openFile(cfg.Root, cfg.NotFoundFile) } - if err != nil { if os.IsNotExist(err) { return c.Status(fiber.StatusNotFound).Next() } - return + return fmt.Errorf("failed to open: %w", err) } - if stat, err = file.Stat(); err != nil { - return + stat, err = file.Stat() + if err != nil { + return fmt.Errorf("failed to stat: %w", err) } // Serve index if path is directory @@ -219,7 +224,7 @@ func New(config ...Config) fiber.Handler { c.Response().SkipBody = true c.Response().Header.SetContentLength(contentLength) if err := file.Close(); err != nil { - return err + return fmt.Errorf("failed to close: %w", err) } return nil } @@ -229,7 +234,7 @@ func New(config ...Config) fiber.Handler { } // SendFile ... -func SendFile(c fiber.Ctx, filesystem fs.FS, path string) (err error) { +func SendFile(c fiber.Ctx, filesystem fs.FS, path string) error { var ( file fs.File stat os.FileInfo @@ -237,16 +242,17 @@ func SendFile(c fiber.Ctx, filesystem fs.FS, path string) (err error) { path = filepath.Join(".", filepath.Clean("/"+path)) - file, err = openFile(filesystem, path) + file, err := openFile(filesystem, path) if err != nil { if os.IsNotExist(err) { return fiber.ErrNotFound } - return err + return fmt.Errorf("failed to open: %w", err) } - if stat, err = file.Stat(); err != nil { - return err + stat, err = file.Stat() + if err != nil { + return fmt.Errorf("failed to stat: %w", err) } // Serve index if path is directory @@ -289,7 +295,7 @@ func SendFile(c fiber.Ctx, filesystem fs.FS, path string) (err error) { c.Response().SkipBody = true c.Response().Header.SetContentLength(contentLength) if err := file.Close(); err != nil { - return err + return fmt.Errorf("failed to close: %w", err) } return nil } diff --git a/middleware/filesystem/filesystem_test.go b/middleware/filesystem/filesystem_test.go index dd2e8c0fbd..5e28b5e288 100644 --- a/middleware/filesystem/filesystem_test.go +++ b/middleware/filesystem/filesystem_test.go @@ -1,6 +1,8 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package filesystem import ( + "context" "net/http" "net/http/httptest" "os" @@ -12,6 +14,7 @@ import ( // go test -run Test_FileSystem func Test_FileSystem(t *testing.T) { + t.Parallel() app := fiber.New() app.Use("/test", New(Config{ @@ -118,7 +121,7 @@ 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)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tt.url, nil)) require.NoError(t, err) require.Equal(t, tt.statusCode, resp.StatusCode) @@ -132,6 +135,7 @@ func Test_FileSystem(t *testing.T) { // go test -run Test_FileSystem_Next func Test_FileSystem_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Root: os.DirFS("../../.github/testdata/fs"), @@ -140,7 +144,7 @@ func Test_FileSystem_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } @@ -153,7 +157,7 @@ func Test_FileSystem_Download(t *testing.T) { Download: true, })) - resp, err := app.Test(httptest.NewRequest("GET", "/img/fiber.png", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/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) == "") @@ -162,6 +166,7 @@ func Test_FileSystem_Download(t *testing.T) { } func Test_FileSystem_NonGetAndHead(t *testing.T) { + t.Parallel() app := fiber.New() app.Use("/test", New(Config{ @@ -174,49 +179,57 @@ func Test_FileSystem_NonGetAndHead(t *testing.T) { } func Test_FileSystem_Head(t *testing.T) { + t.Parallel() app := fiber.New() app.Use("/test", New(Config{ Root: os.DirFS("../../.github/testdata/fs"), })) - req, _ := http.NewRequest(fiber.MethodHead, "/test", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/test", nil) + require.NoError(t, err) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) } func Test_FileSystem_NoRoot(t *testing.T) { + t.Parallel() defer func() { require.Equal(t, "filesystem: Root cannot be nil", recover()) }() app := fiber.New() app.Use(New()) - _, _ = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + require.NoError(t, err) } func Test_FileSystem_UsingParam(t *testing.T) { + t.Parallel() app := fiber.New() app.Use("/:path", func(c fiber.Ctx) error { return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html") }) - req, _ := http.NewRequest(fiber.MethodHead, "/index", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/index", nil) + require.NoError(t, err) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) } func Test_FileSystem_UsingParam_NonFile(t *testing.T) { + t.Parallel() app := fiber.New() app.Use("/:path", func(c fiber.Ctx) error { return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html") }) - req, _ := http.NewRequest(fiber.MethodHead, "/template", nil) + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/template", nil) + require.NoError(t, err) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, 404, resp.StatusCode) diff --git a/middleware/filesystem/utils.go b/middleware/filesystem/utils.go index 3561870a0a..00247a91bf 100644 --- a/middleware/filesystem/utils.go +++ b/middleware/filesystem/utils.go @@ -12,19 +12,19 @@ import ( "github.com/gofiber/fiber/v3" ) -func getFileExtension(path string) string { - n := strings.LastIndexByte(path, '.') +func getFileExtension(p string) string { + n := strings.LastIndexByte(p, '.') if n < 0 { return "" } - return path[n:] + return p[n:] } func dirList(c fiber.Ctx, f fs.File) error { ff := f.(fs.ReadDirFile) fileinfos, err := ff.ReadDir(-1) if err != nil { - return err + return fmt.Errorf("failed to read dir: %w", err) } fm := make(map[string]fs.FileInfo, len(fileinfos)) @@ -41,13 +41,13 @@ func dirList(c fiber.Ctx, f fs.File) error { } basePathEscaped := html.EscapeString(c.Path()) - fmt.Fprintf(c, "%s ", basePathEscaped) - fmt.Fprintf(c, "

%s

", basePathEscaped) - fmt.Fprint(c, "
    ") + _, _ = fmt.Fprintf(c, "%s ", basePathEscaped) + _, _ = fmt.Fprintf(c, "

    %s

    ", basePathEscaped) + _, _ = fmt.Fprint(c, "
      ") if len(basePathEscaped) > 1 { parentPathEscaped := html.EscapeString(strings.TrimRight(c.Path(), "/") + "/..") - fmt.Fprintf(c, `
    • ..
    • `, parentPathEscaped) + _, _ = fmt.Fprintf(c, `
    • ..
    • `, parentPathEscaped) } sort.Strings(filenames) @@ -60,10 +60,10 @@ func dirList(c fiber.Ctx, f fs.File) error { auxStr = fmt.Sprintf("file, %d bytes", fi.Size()) className = "file" } - fmt.Fprintf(c, `
    • %s, %s, last modified %s
    • `, + _, _ = fmt.Fprintf(c, `
    • %s, %s, last modified %s
    • `, pathEscaped, className, html.EscapeString(name), auxStr, fi.ModTime()) } - fmt.Fprint(c, "
    ") + _, _ = fmt.Fprint(c, "
") c.Type("html") diff --git a/middleware/idempotency/idempotency_test.go b/middleware/idempotency/idempotency_test.go index d289124555..1873050ae2 100644 --- a/middleware/idempotency/idempotency_test.go +++ b/middleware/idempotency/idempotency_test.go @@ -1,3 +1,4 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package idempotency_test import ( @@ -171,5 +172,4 @@ func Benchmark_Idempotency(b *testing.B) { h(c) } }) - } diff --git a/middleware/limiter/README.md b/middleware/limiter/README.md index 919764bf14..9542515f85 100644 --- a/middleware/limiter/README.md +++ b/middleware/limiter/README.md @@ -52,8 +52,8 @@ app.Use(limiter.New(limiter.Config{ Next: func(c fiber.Ctx) bool { return c.IP() == "127.0.0.1" }, - Max: 20, - Expiration: 30 * time.Second, + Max: 20, + Expiration: 30 * time.Second, KeyGenerator: func(c fiber.Ctx) string{ return "key" } diff --git a/middleware/limiter/limiter_test.go b/middleware/limiter/limiter_test.go index 9cae095c11..dd4ba87305 100644 --- a/middleware/limiter/limiter_test.go +++ b/middleware/limiter/limiter_test.go @@ -2,7 +2,6 @@ package limiter import ( "io" - "net/http" "net/http/httptest" "sync" "testing" @@ -16,6 +15,7 @@ import ( // go test -run Test_Limiter_Concurrency_Store -race -v func Test_Limiter_Concurrency_Store(t *testing.T) { + t.Parallel() // Test concurrency using a custom store app := fiber.New() @@ -33,7 +33,7 @@ func Test_Limiter_Concurrency_Store(t *testing.T) { var wg sync.WaitGroup singleRequest := func(wg *sync.WaitGroup) { defer wg.Done() - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -49,19 +49,20 @@ func Test_Limiter_Concurrency_Store(t *testing.T) { wg.Wait() - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_Concurrency -race -v func Test_Limiter_Concurrency(t *testing.T) { + t.Parallel() // Test concurrency using a default store app := fiber.New() @@ -78,7 +79,7 @@ func Test_Limiter_Concurrency(t *testing.T) { var wg sync.WaitGroup singleRequest := func(wg *sync.WaitGroup) { defer wg.Done() - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -94,19 +95,20 @@ func Test_Limiter_Concurrency(t *testing.T) { wg.Wait() - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, 429, resp.StatusCode) time.Sleep(3 * time.Second) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_No_Skip_Choices -v func Test_Limiter_No_Skip_Choices(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -117,27 +119,28 @@ func Test_Limiter_No_Skip_Choices(t *testing.T) { })) app.Get("/:status", func(c fiber.Ctx) error { - if c.Params("status") == "fail" { + if c.Params("status") == "fail" { //nolint:goconst // False positive return c.SendStatus(400) } return c.SendStatus(200) }) - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil)) require.NoError(t, err) require.Equal(t, 400, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) require.NoError(t, err) require.Equal(t, 429, resp.StatusCode) } // go test -run Test_Limiter_Skip_Failed_Requests -v func Test_Limiter_Skip_Failed_Requests(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -153,27 +156,28 @@ func Test_Limiter_Skip_Failed_Requests(t *testing.T) { return c.SendStatus(200) }) - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil)) require.NoError(t, err) require.Equal(t, 400, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) } // go test -run Test_Limiter_Skip_Successful_Requests -v func Test_Limiter_Skip_Successful_Requests(t *testing.T) { + t.Parallel() // Test concurrency using a default store app := fiber.New() @@ -191,21 +195,21 @@ func Test_Limiter_Skip_Successful_Requests(t *testing.T) { return c.SendStatus(200) }) - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/success", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil)) require.NoError(t, err) require.Equal(t, 200, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil)) require.NoError(t, err) require.Equal(t, 400, resp.StatusCode) - resp, err = app.Test(httptest.NewRequest(http.MethodGet, "/fail", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil)) require.NoError(t, err) require.Equal(t, 400, resp.StatusCode) } @@ -227,7 +231,7 @@ func Benchmark_Limiter_Custom_Store(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") b.ResetTimer() @@ -239,6 +243,7 @@ func Benchmark_Limiter_Custom_Store(b *testing.B) { // go test -run Test_Limiter_Next func Test_Limiter_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -246,12 +251,13 @@ func Test_Limiter_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_Limiter_Headers(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ @@ -264,7 +270,7 @@ func Test_Limiter_Headers(t *testing.T) { }) fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") app.Handler()(fctx) @@ -294,7 +300,7 @@ func Benchmark_Limiter(b *testing.B) { h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") b.ResetTimer() @@ -306,6 +312,7 @@ func Benchmark_Limiter(b *testing.B) { // go test -run Test_Sliding_Window -race -v func Test_Sliding_Window(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Max: 10, @@ -319,7 +326,7 @@ func Test_Sliding_Window(t *testing.T) { }) singleRequest := func(shouldFail bool) { - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) if shouldFail { require.NoError(t, err) require.Equal(t, 429, resp.StatusCode) diff --git a/middleware/limiter/manager.go b/middleware/limiter/manager.go index 46d7c4b6af..4225cdd3ac 100644 --- a/middleware/limiter/manager.go +++ b/middleware/limiter/manager.go @@ -8,7 +8,8 @@ import ( "github.com/gofiber/fiber/v3/internal/memory" ) -//go:generate msgp -o=manager_msgp.go -io=false -unexported +// go:generate msgp +// msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported type item struct { currHits int prevHits int @@ -43,7 +44,7 @@ func newManager(storage fiber.Storage) *manager { // acquire returns an *entry from the sync.Pool func (m *manager) acquire() *item { - return m.pool.Get().(*item) + return m.pool.Get().(*item) //nolint:forcetypeassert // We store nothing else in the pool } // release and reset *entry to sync.Pool @@ -55,37 +56,33 @@ func (m *manager) release(e *item) { } // get data from storage or memory -func (m *manager) get(key string) (it *item) { +func (m *manager) get(key string) *item { + var it *item if m.storage != nil { it = m.acquire() - if raw, _ := m.storage.Get(key); raw != nil { + raw, err := m.storage.Get(key) + if err != nil { + return it + } + if raw != nil { if _, err := it.UnmarshalMsg(raw); err != nil { - return + return it } } - return + return it } - if it, _ = m.memory.Get(key).(*item); it == nil { + if it, _ = m.memory.Get(key).(*item); it == nil { //nolint:errcheck // We store nothing else in the pool it = m.acquire() + return it } - return -} - -// get raw data from storage or memory -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 + return it } // set data to storage or memory func (m *manager) set(key string, it *item, exp time.Duration) { if m.storage != nil { if raw, err := it.MarshalMsg(nil); err == nil { - _ = m.storage.Set(key, raw, exp) + _ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Handle error here } // we can release data because it's serialized to database m.release(it) @@ -93,21 +90,3 @@ func (m *manager) set(key string, it *item, exp time.Duration) { m.memory.Set(key, it, exp) } } - -// set data to storage or memory -func (m *manager) setRaw(key string, raw []byte, exp time.Duration) { - 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) { - 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 411dc06a81..70cbefdfd3 100644 --- a/middleware/logger/README.md +++ b/middleware/logger/README.md @@ -96,7 +96,7 @@ app.Use(logger.New(logger.Config{ TimeZone: "Asia/Shanghai", Done: func(c fiber.Ctx, logString []byte) { if c.Response().StatusCode() != fiber.StatusOK { - reporter.SendToSlack(logString) + reporter.SendToSlack(logString) } }, })) @@ -246,7 +246,7 @@ const ( TagBytesReceived = "bytesReceived" TagRoute = "route" TagError = "error" - // DEPRECATED: Use TagReqHeader instead + // Deprecated: Use TagReqHeader instead TagHeader = "header:" // request header TagReqHeader = "reqHeader:" // request header TagRespHeader = "respHeader:" // response header diff --git a/middleware/logger/config.go b/middleware/logger/config.go index a30f3cd26c..ceae3dd445 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -161,7 +161,7 @@ func configDefault(config ...Config) Config { } // Enable colors if no custom format or output is given - if cfg.Output == nil && checkColorEnable(cfg.Format) { + if cfg.Output == ConfigDefault.Output && checkColorEnable(cfg.Format) { cfg.enableColors = true } diff --git a/middleware/logger/data.go b/middleware/logger/data.go index fc30238835..2d5955dc51 100644 --- a/middleware/logger/data.go +++ b/middleware/logger/data.go @@ -1,13 +1,10 @@ package logger import ( - "sync" "sync/atomic" "time" ) -var DataPool = sync.Pool{New: func() any { return new(Data) }} - // Data is a struct to define some variables to use in custom logger function. type Data struct { Pid string diff --git a/middleware/logger/default_logger.go b/middleware/logger/default_logger.go index 624725ed6c..a6dee891a1 100644 --- a/middleware/logger/default_logger.go +++ b/middleware/logger/default_logger.go @@ -18,15 +18,14 @@ var mu sync.Mutex // default logger for fiber func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { + var err error + // Alias colors colors := c.App().Config().ColorScheme // Get new buffer buf := bytebufferpool.Get() - // Put buffer back to pool - defer bytebufferpool.Put(buf) - // Default output when no custom Format or io.Writer is given if cfg.enableColors && cfg.Format == defaultFormat { // Format error if exist @@ -36,32 +35,36 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { } // 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, - )) + _, _ = buf.WriteString( //nolint:errcheck // This will never fail + 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()) + _, _ = cfg.Output.Write(buf.Bytes()) //nolint:errcheck // This will never fail if cfg.Done != nil { cfg.Done(c, buf.Bytes()) } + // Put buffer back to pool + bytebufferpool.Put(buf) + // End chain return nil } - var err error // Loop over template parts execute dynamic parts and add fixed parts to the buffer for i, logFunc := range data.LogFuncChain { if logFunc == nil { - _, _ = buf.Write(data.TemplateChain[i]) + _, _ = buf.Write(data.TemplateChain[i]) //nolint:errcheck // This will never fail } else if data.TemplateChain[i] == nil { _, err = logFunc(buf, c, data, "") } else { @@ -74,7 +77,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { // Also write errors to the buffer if err != nil { - _, _ = buf.WriteString(err.Error()) + _, _ = buf.WriteString(err.Error()) //nolint:errcheck // This will never fail } mu.Lock() // Write buffer to output @@ -82,7 +85,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { // 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) + _, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) } } mu.Unlock() @@ -91,6 +94,9 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { cfg.Done(c, buf.Bytes()) } + // Put buffer back to pool + bytebufferpool.Put(buf) + return nil } diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 489c52f5b2..bcbd047e74 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -48,6 +48,8 @@ func New(config ...Config) fiber.Handler { var ( once sync.Once errHandler fiber.ErrorHandler + + dataPool = sync.Pool{New: func() interface{} { return new(Data) }} ) // Err padding @@ -89,7 +91,7 @@ func New(config ...Config) fiber.Handler { }) // Logger data - data := DataPool.Get().(*Data) + data := dataPool.Get().(*Data) //nolint:forcetypeassert,errcheck // We store nothing else in the pool // no need for a reset, as long as we always override everything data.Pid = pid data.ErrPaddingStr = errPaddingStr @@ -97,7 +99,7 @@ func New(config ...Config) fiber.Handler { data.TemplateChain = templateChain data.LogFuncChain = logFunChain // put data back in the pool - defer DataPool.Put(data) + defer dataPool.Put(data) // Set latency start time if cfg.enableLatency { @@ -111,7 +113,7 @@ func New(config ...Config) fiber.Handler { // Manually call error handler if chainErr != nil { if err := errHandler(c, chainErr); err != nil { - _ = c.SendStatus(fiber.StatusInternalServerError) + _ = c.SendStatus(fiber.StatusInternalServerError) //nolint:errcheck // TODO: Explain why we ignore the error here } } diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 76f485b650..536459c7e0 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -1,6 +1,8 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package logger import ( + "bufio" "bytes" "errors" "fmt" @@ -10,6 +12,7 @@ import ( "os" "sync" "testing" + "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/requestid" @@ -20,6 +23,7 @@ import ( // go test -run Test_Logger func Test_Logger(t *testing.T) { + t.Parallel() app := fiber.New() buf := bytebufferpool.Get() @@ -34,7 +38,7 @@ func Test_Logger(t *testing.T) { return errors.New("some random error") }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) require.Equal(t, "some random error", buf.String()) @@ -42,6 +46,7 @@ func Test_Logger(t *testing.T) { // go test -run Test_Logger_locals func Test_Logger_locals(t *testing.T) { + t.Parallel() app := fiber.New() buf := bytebufferpool.Get() @@ -66,21 +71,21 @@ func Test_Logger_locals(t *testing.T) { return c.SendStatus(fiber.StatusOK) }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/int", nil)) 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)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/empty", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, "", buf.String()) @@ -88,6 +93,7 @@ func Test_Logger_locals(t *testing.T) { // go test -run Test_Logger_Next func Test_Logger_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -95,26 +101,28 @@ func Test_Logger_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } // go test -run Test_Logger_Done func Test_Logger_Done(t *testing.T) { + t.Parallel() buf := bytes.NewBuffer(nil) app := fiber.New() app.Use(New(Config{ Done: func(c fiber.Ctx, logString []byte) { if c.Response().StatusCode() == fiber.StatusOK { - buf.Write(logString) + _, err := buf.Write(logString) + require.NoError(t, err) } }, })).Get("/logging", func(ctx fiber.Ctx) error { return ctx.SendStatus(fiber.StatusOK) }) - resp, err := app.Test(httptest.NewRequest("GET", "/logging", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/logging", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -123,12 +131,13 @@ func Test_Logger_Done(t *testing.T) { // go test -run Test_Logger_ErrorTimeZone func Test_Logger_ErrorTimeZone(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ TimeZone: "invalid", })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } @@ -142,13 +151,14 @@ func (o *fakeOutput) Write([]byte) (int, error) { // go test -run Test_Logger_ErrorOutput func Test_Logger_ErrorOutput(t *testing.T) { + t.Parallel() o := new(fakeOutput) app := fiber.New() app.Use(New(Config{ Output: o, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) @@ -157,6 +167,7 @@ func Test_Logger_ErrorOutput(t *testing.T) { // go test -run Test_Logger_All func Test_Logger_All(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -169,7 +180,7 @@ func Test_Logger_All(t *testing.T) { // Alias colors colors := app.Config().ColorScheme - resp, err := app.Test(httptest.NewRequest("GET", "/?foo=bar", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) @@ -179,6 +190,7 @@ func Test_Logger_All(t *testing.T) { // go test -run Test_Query_Params func Test_Query_Params(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -188,7 +200,7 @@ func Test_Query_Params(t *testing.T) { Output: buf, })) - resp, err := app.Test(httptest.NewRequest("GET", "/?foo=bar&baz=moz", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar&baz=moz", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) @@ -198,6 +210,7 @@ func Test_Query_Params(t *testing.T) { // go test -run Test_Response_Body func Test_Response_Body(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -215,7 +228,7 @@ func Test_Response_Body(t *testing.T) { return c.Send([]byte("Post in test")) }) - _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) expectedGetResponse := "Sample response body" @@ -223,7 +236,7 @@ func Test_Response_Body(t *testing.T) { buf.Reset() // Reset buffer to test POST - _, err = app.Test(httptest.NewRequest("POST", "/test", nil)) + _, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil)) require.NoError(t, err) expectedPostResponse := "Post in test" @@ -232,6 +245,7 @@ func Test_Response_Body(t *testing.T) { // go test -run Test_Logger_AppendUint func Test_Logger_AppendUint(t *testing.T) { + t.Parallel() app := fiber.New() buf := bytebufferpool.Get() @@ -246,7 +260,7 @@ func Test_Logger_AppendUint(t *testing.T) { return c.SendString("hello") }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, "0 5 200", buf.String()) @@ -254,12 +268,13 @@ func Test_Logger_AppendUint(t *testing.T) { // go test -run Test_Logger_Data_Race -race func Test_Logger_Data_Race(t *testing.T) { + t.Parallel() app := fiber.New() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) - app.Use(New(ConfigDefault)) + app.Use(New()) app.Get("/", func(c fiber.Ctx) error { return c.SendString("hello") @@ -272,10 +287,10 @@ func Test_Logger_Data_Race(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) go func() { - resp1, err1 = app.Test(httptest.NewRequest("GET", "/", nil)) + resp1, err1 = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) wg.Done() }() - resp2, err2 = app.Test(httptest.NewRequest("GET", "/", nil)) + resp2, err2 = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) wg.Wait() require.Nil(t, err1) @@ -286,21 +301,23 @@ func Test_Logger_Data_Race(t *testing.T) { // go test -v -run=^$ -bench=Benchmark_Logger -benchmem -count=4 func Benchmark_Logger(b *testing.B) { - benchSetup := func(bb *testing.B, app *fiber.App) { + benchSetup := func(b *testing.B, app *fiber.App) { + b.Helper() + h := app.Handler() fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") + fctx.Request.Header.SetMethod(fiber.MethodGet) fctx.Request.SetRequestURI("/") - bb.ReportAllocs() - bb.ResetTimer() + b.ReportAllocs() + b.ResetTimer() - for n := 0; n < bb.N; n++ { + for n := 0; n < b.N; n++ { h(fctx) } - require.Equal(bb, 200, fctx.Response.Header.StatusCode()) + require.Equal(b, 200, fctx.Response.Header.StatusCode()) } b.Run("Base", func(bb *testing.B) { @@ -343,6 +360,7 @@ func Benchmark_Logger(b *testing.B) { // go test -run Test_Response_Header func Test_Response_Header(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -361,7 +379,7 @@ func Test_Response_Header(t *testing.T) { return c.SendString("Hello fiber!") }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -370,6 +388,7 @@ func Test_Response_Header(t *testing.T) { // go test -run Test_Req_Header func Test_Req_Header(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -381,10 +400,10 @@ func Test_Req_Header(t *testing.T) { app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello fiber!") }) - headerReq := httptest.NewRequest("GET", "/", nil) + headerReq := httptest.NewRequest(fiber.MethodGet, "/", nil) headerReq.Header.Add("test", "Hello fiber!") - resp, err := app.Test(headerReq) + resp, err := app.Test(headerReq) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, "Hello fiber!", buf.String()) @@ -392,6 +411,7 @@ func Test_Req_Header(t *testing.T) { // go test -run Test_ReqHeader_Header func Test_ReqHeader_Header(t *testing.T) { + t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) @@ -403,10 +423,10 @@ func Test_ReqHeader_Header(t *testing.T) { app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello fiber!") }) - reqHeaderReq := httptest.NewRequest("GET", "/", nil) + reqHeaderReq := httptest.NewRequest(fiber.MethodGet, "/", nil) reqHeaderReq.Header.Add("test", "Hello fiber!") - resp, err := app.Test(reqHeaderReq) + resp, err := app.Test(reqHeaderReq) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, "Hello fiber!", buf.String()) @@ -414,6 +434,7 @@ func Test_ReqHeader_Header(t *testing.T) { // go test -run Test_CustomTags func Test_CustomTags(t *testing.T) { + t.Parallel() customTag := "it is a custom tag" buf := bytebufferpool.Get() @@ -432,11 +453,51 @@ func Test_CustomTags(t *testing.T) { app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello fiber!") }) - reqHeaderReq := httptest.NewRequest("GET", "/", nil) + reqHeaderReq := httptest.NewRequest(fiber.MethodGet, "/", nil) reqHeaderReq.Header.Add("test", "Hello fiber!") - resp, err := app.Test(reqHeaderReq) + resp, err := app.Test(reqHeaderReq) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, customTag, buf.String()) } + +// go test -run Test_Logger_ByteSent_Streaming +func Test_Logger_ByteSent_Streaming(t *testing.T) { + t.Parallel() + app := fiber.New() + + buf := bytebufferpool.Get() + defer bytebufferpool.Put(buf) + + app.Use(New(Config{ + Format: "${bytesReceived} ${bytesSent} ${status}", + Output: buf, + })) + + app.Get("/", func(c fiber.Ctx) error { + c.Set("Connection", "keep-alive") + c.Set("Transfer-Encoding", "chunked") + c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { + var i int + for { + i++ + msg := fmt.Sprintf("%d - the time is %v", i, time.Now()) + fmt.Fprintf(w, "data: Message: %s\n\n", msg) + err := w.Flush() + if err != nil { + break + } + if i == 10 { + break + } + } + }) + return nil + }) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, "0 0 200", buf.String()) +} diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go index feb473ad87..afc0e34ad4 100644 --- a/middleware/logger/tags.go +++ b/middleware/logger/tags.go @@ -3,7 +3,6 @@ package logger import ( "fmt" "strings" - "time" "github.com/gofiber/fiber/v3" ) @@ -91,6 +90,9 @@ func createTagMap(cfg *Config) map[string]LogFunc { return appendInt(output, len(c.Request().Body())) }, TagBytesSent: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + if c.Response().Header.ContentLength() < 0 { + return appendInt(output, 0) + } return appendInt(output, len(c.Response().Body())) }, TagRoute: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { @@ -193,11 +195,11 @@ func createTagMap(cfg *Config) map[string]LogFunc { return output.WriteString(data.Pid) }, TagLatency: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { - latency := data.Stop.Sub(data.Start).Round(time.Millisecond) + latency := data.Stop.Sub(data.Start) return output.WriteString(fmt.Sprintf("%7v", latency)) }, TagTime: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { - return output.WriteString(data.Timestamp.Load().(string)) + return output.WriteString(data.Timestamp.Load().(string)) //nolint:forcetypeassert // We always store a string in here }, } // merge with custom tags from user diff --git a/middleware/logger/template_chain.go b/middleware/logger/template_chain.go index ceb31a3356..1a5dd448f6 100644 --- a/middleware/logger/template_chain.go +++ b/middleware/logger/template_chain.go @@ -14,13 +14,16 @@ import ( // funcChain contains for the parts which exist the functions for the dynamic parts // funcChain and fixParts always have the same length and contain nil for the parts where no data is required in the chain, // if a function exists for the part, a parameter for it can also exist in the fixParts slice -func buildLogFuncChain(cfg *Config, tagFunctions map[string]LogFunc) (fixParts [][]byte, funcChain []LogFunc, err error) { +func buildLogFuncChain(cfg *Config, tagFunctions map[string]LogFunc) ([][]byte, []LogFunc, error) { // process flow is copied from the fasttemplate flow https://github.com/valyala/fasttemplate/blob/2a2d1afadadf9715bfa19683cdaeac8347e5d9f9/template.go#L23-L62 templateB := utils.UnsafeBytes(cfg.Format) startTagB := utils.UnsafeBytes(startTag) endTagB := utils.UnsafeBytes(endTag) paramSeparatorB := utils.UnsafeBytes(paramSeparator) + var fixParts [][]byte + var funcChain []LogFunc + for { currentPos := bytes.Index(templateB, startTagB) if currentPos < 0 { @@ -42,13 +45,13 @@ func buildLogFuncChain(cfg *Config, tagFunctions map[string]LogFunc) (fixParts [ // ## function block ## // first check for tags with parameters if index := bytes.Index(templateB[:currentPos], paramSeparatorB); index != -1 { - if logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:index+1])]; ok { - funcChain = append(funcChain, logFunc) - // add param to the fixParts - fixParts = append(fixParts, templateB[index+1:currentPos]) - } else { + logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:index+1])] + if !ok { return nil, nil, errors.New("No parameter found in \"" + utils.UnsafeString(templateB[:currentPos]) + "\"") } + funcChain = append(funcChain, logFunc) + // add param to the fixParts + fixParts = append(fixParts, templateB[index+1:currentPos]) } else if logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:currentPos])]; ok { // add functions without parameter funcChain = append(funcChain, logFunc) @@ -63,5 +66,5 @@ func buildLogFuncChain(cfg *Config, tagFunctions map[string]LogFunc) (fixParts [ funcChain = append(funcChain, nil) fixParts = append(fixParts, templateB) - return + return fixParts, funcChain, nil } diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go index aa55dc0181..373e42d6f0 100644 --- a/middleware/pprof/pprof.go +++ b/middleware/pprof/pprof.go @@ -8,26 +8,26 @@ import ( "github.com/valyala/fasthttp/fasthttpadaptor" ) -// Set pprof adaptors -var ( - pprofIndex = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Index) - pprofCmdline = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Cmdline) - pprofProfile = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Profile) - pprofSymbol = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Symbol) - pprofTrace = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Trace) - pprofAllocs = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("allocs").ServeHTTP) - pprofBlock = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("block").ServeHTTP) - pprofGoroutine = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("goroutine").ServeHTTP) - pprofHeap = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("heap").ServeHTTP) - pprofMutex = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("mutex").ServeHTTP) - pprofThreadcreate = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("threadcreate").ServeHTTP) -) - // New creates a new middleware handler func New(config ...Config) fiber.Handler { // Set default config cfg := configDefault(config...) + // Set pprof adaptors + var ( + pprofIndex = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Index) + pprofCmdline = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Cmdline) + pprofProfile = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Profile) + pprofSymbol = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Symbol) + pprofTrace = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Trace) + pprofAllocs = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("allocs").ServeHTTP) + pprofBlock = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("block").ServeHTTP) + pprofGoroutine = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("goroutine").ServeHTTP) + pprofHeap = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("heap").ServeHTTP) + pprofMutex = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("mutex").ServeHTTP) + pprofThreadcreate = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("threadcreate").ServeHTTP) + ) + // Return new handler return func(c fiber.Ctx) error { // Don't execute middleware if Next returns true diff --git a/middleware/pprof/pprof_test.go b/middleware/pprof/pprof_test.go index c4274f1a85..afb152f743 100644 --- a/middleware/pprof/pprof_test.go +++ b/middleware/pprof/pprof_test.go @@ -12,6 +12,7 @@ import ( ) func Test_Non_Pprof_Path(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -30,6 +31,7 @@ func Test_Non_Pprof_Path(t *testing.T) { } func Test_Non_Pprof_Path_WithPrefix(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Prefix: "/federated-fiber"})) @@ -48,6 +50,7 @@ func Test_Non_Pprof_Path_WithPrefix(t *testing.T) { } func Test_Pprof_Index(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -67,6 +70,7 @@ func Test_Pprof_Index(t *testing.T) { } func Test_Pprof_Index_WithPrefix(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Prefix: "/federated-fiber"})) @@ -86,6 +90,7 @@ func Test_Pprof_Index_WithPrefix(t *testing.T) { } func Test_Pprof_Subs(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -101,6 +106,7 @@ func Test_Pprof_Subs(t *testing.T) { for _, sub := range subs { t.Run(sub, func(t *testing.T) { + t.Parallel() target := "/debug/pprof/" + sub if sub == "profile" { target += "?seconds=1" @@ -113,6 +119,7 @@ func Test_Pprof_Subs(t *testing.T) { } func Test_Pprof_Subs_WithPrefix(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Prefix: "/federated-fiber"})) @@ -128,6 +135,7 @@ func Test_Pprof_Subs_WithPrefix(t *testing.T) { for _, sub := range subs { t.Run(sub, func(t *testing.T) { + t.Parallel() target := "/federated-fiber/debug/pprof/" + sub if sub == "profile" { target += "?seconds=1" @@ -140,6 +148,7 @@ func Test_Pprof_Subs_WithPrefix(t *testing.T) { } func Test_Pprof_Other(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -154,6 +163,7 @@ func Test_Pprof_Other(t *testing.T) { } func Test_Pprof_Other_WithPrefix(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{Prefix: "/federated-fiber"})) @@ -170,7 +180,6 @@ func Test_Pprof_Other_WithPrefix(t *testing.T) { // go test -run Test_Pprof_Next func Test_Pprof_Next(t *testing.T) { t.Parallel() - app := fiber.New() app.Use(New(Config{ @@ -187,7 +196,6 @@ func Test_Pprof_Next(t *testing.T) { // go test -run Test_Pprof_Next_WithPrefix func Test_Pprof_Next_WithPrefix(t *testing.T) { t.Parallel() - app := fiber.New() app.Use(New(Config{ diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md index daa66245fc..f49acffe49 100644 --- a/middleware/proxy/README.md +++ b/middleware/proxy/README.md @@ -12,9 +12,16 @@ Proxy middleware for [Fiber](https://github.com/gofiber/fiber) that allows you t ### Signatures ```go +// Balancer create a load balancer among multiple upstrem servers. func Balancer(config Config) fiber.Handler +// Forward performs the given http request and fills the given http response. func Forward(addr string, clients ...*fasthttp.Client) fiber.Handler -func Do(c fiber.Ctx, addr string, clients ...*fasthttp.Client) error +// Do performs the given http request and fills the given http response. +func Do(c *iber.Ctx, addr string, clients ...*fasthttp.Client) error +// DomainForward the given http request based on the given domain and fills the given http response +func DomainForward(hostname string, addr string, clients ...*fasthttp.Client) fiber.Handler +// BalancerForward performs the given http request based round robin balancer and fills the given http response +func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler ``` ### Examples @@ -23,8 +30,8 @@ Import the middleware package that is part of the Fiber web framework ```go import ( - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/middleware/proxy" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/proxy" ) ``` @@ -39,54 +46,64 @@ proxy.WithTlsConfig(&tls.Config{ // if you need to use global self-custom client, you should use proxy.WithClient. proxy.WithClient(&fasthttp.Client{ - NoDefaultUserAgentHeader: true, - DisablePathNormalizing: true, + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, }) // Forward to url app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif")) +// If you want to forward with a specific domain. You have to use proxy.DomainForward. +app.Get("/payments", proxy.DomainForward("docs.gofiber.io", "http://localhost:8000")) + // Forward to url with local custom client app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif", &fasthttp.Client{ - NoDefaultUserAgentHeader: true, - DisablePathNormalizing: true, + 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" - if err := proxy.Do(c, url); err != nil { - return err - } - // Remove Server header from response - c.Response().Header.Del(fiber.HeaderServer) - return nil + url := "https://i.imgur.com/"+c.Params("id")+".gif" + if err := proxy.Do(c, url); err != nil { + return err + } + // Remove Server header from response + c.Response().Header.Del(fiber.HeaderServer) + return nil }) // Minimal round robin balancer app.Use(proxy.Balancer(proxy.Config{ - Servers: []string{ - "http://localhost:3001", - "http://localhost:3002", - "http://localhost:3003", - }, + Servers: []string{ + "http://localhost:3001", + "http://localhost:3002", + "http://localhost:3003", + }, })) // Or extend your balancer for customization app.Use(proxy.Balancer(proxy.Config{ - Servers: []string{ - "http://localhost:3001", - "http://localhost:3002", - "http://localhost:3003", - }, - ModifyRequest: func(c fiber.Ctx) error { - c.Request().Header.Add("X-Real-IP", c.IP()) - return nil - }, - ModifyResponse: func(c fiber.Ctx) error { - c.Response().Header.Del(fiber.HeaderServer) - return nil - }, + Servers: []string{ + "http://localhost:3001", + "http://localhost:3002", + "http://localhost:3003", + }, + ModifyRequest: func(c fiber.Ctx) error { + c.Request().Header.Add("X-Real-IP", c.IP()) + return nil + }, + ModifyResponse: func(c fiber.Ctx) error { + c.Response().Header.Del(fiber.HeaderServer) + return nil + }, +})) + +// Or this way if the balancer is using https and the destination server is only using http. +app.Use(proxy.BalancerForward([]string{ + "http://localhost:3001", + "http://localhost:3002", + "http://localhost:3003", })) ``` @@ -95,50 +112,50 @@ app.Use(proxy.Balancer(proxy.Config{ ```go // Config defines the config for middleware. type Config struct { - // Next defines a function to skip this middleware when returned true. - // - // Optional. Default: nil - Next func(c fiber.Ctx) bool - - // Servers defines a list of :// HTTP servers, - // - // which are used in a round-robin manner. - // i.e.: "https://foobar.com, http://www.foobar.com" - // - // Required - Servers []string - - // ModifyRequest allows you to alter the request - // - // Optional. Default: nil - ModifyRequest fiber.Handler - - // ModifyResponse allows you to alter the response - // - // Optional. Default: nil - ModifyResponse fiber.Handler - - // Timeout is the request timeout used when calling the proxy client - // - // Optional. Default: 1 second - Timeout time.Duration - - // Per-connection buffer size for requests' reading. - // This also limits the maximum header size. - // Increase this buffer if your clients send multi-KB RequestURIs - // and/or multi-KB headers (for example, BIG cookies). - ReadBufferSize int + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c fiber.Ctx) bool + + // Servers defines a list of :// HTTP servers, + // + // which are used in a round-robin manner. + // i.e.: "https://foobar.com, http://www.foobar.com" + // + // Required + Servers []string + + // ModifyRequest allows you to alter the request + // + // Optional. Default: nil + ModifyRequest fiber.Handler + + // ModifyResponse allows you to alter the response + // + // Optional. Default: nil + ModifyResponse fiber.Handler + + // Timeout is the request timeout used when calling the proxy client + // + // Optional. Default: 1 second + Timeout time.Duration + + // Per-connection buffer size for requests' reading. + // This also limits the maximum header size. + // Increase this buffer if your clients send multi-KB RequestURIs + // and/or multi-KB headers (for example, BIG cookies). + ReadBufferSize int - // Per-connection buffer size for responses' writing. - WriteBufferSize int - - // 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 + // Per-connection buffer size for responses' writing. + WriteBufferSize int + + // 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 0a482c6a55..18959748d2 100644 --- a/middleware/proxy/config.go +++ b/middleware/proxy/config.go @@ -48,7 +48,7 @@ type Config struct { WriteBufferSize int // tls config for the http client. - TlsConfig *tls.Config + TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3 // Client is custom client when client config is complex. // Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 6ce4ba0b61..ee4b923570 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -18,7 +18,7 @@ func Balancer(config Config) fiber.Handler { cfg := configDefault(config) // Load balanced client - var lbc = &fasthttp.LBClient{} + lbc := &fasthttp.LBClient{} // Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig // will not be used if the client are set. if config.Client == nil { @@ -54,7 +54,7 @@ func Balancer(config Config) fiber.Handler { } // Return new handler - return func(c fiber.Ctx) (err error) { + return func(c fiber.Ctx) error { // Don't execute middleware if Next returns true if cfg.Next != nil && cfg.Next(c) { return c.Next() @@ -69,7 +69,7 @@ func Balancer(config Config) fiber.Handler { // Modify request if cfg.ModifyRequest != nil { - if err = cfg.ModifyRequest(c); err != nil { + if err := cfg.ModifyRequest(c); err != nil { return err } } @@ -77,7 +77,7 @@ func Balancer(config Config) fiber.Handler { req.SetRequestURI(utils.UnsafeString(req.RequestURI())) // Forward request - if err = lbc.Do(req, res); err != nil { + if err := lbc.Do(req, res); err != nil { return err } @@ -86,7 +86,7 @@ func Balancer(config Config) fiber.Handler { // Modify response if cfg.ModifyResponse != nil { - if err = cfg.ModifyResponse(c); err != nil { + if err := cfg.ModifyResponse(c); err != nil { return err } } @@ -106,6 +106,8 @@ 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. +// +//nolint:stylecheck,revive // TODO: Rename to "WithTLSConfig" in v3 func WithTlsConfig(tlsConfig *tls.Config) { client.TLSConfig = tlsConfig } @@ -168,3 +170,53 @@ func getScheme(uri []byte) []byte { } return uri[:i-1] } + +// DomainForward performs an http request based on the given domain and populates the given http response. +// This method will return an fiber.Handler +func DomainForward(hostname, addr string, clients ...*fasthttp.Client) fiber.Handler { + return func(c fiber.Ctx) error { + host := string(c.Request().Host()) + if host == hostname { + return Do(c, addr+c.OriginalURL(), clients...) + } + return nil + } +} + +type roundrobin struct { + sync.Mutex + + current int + pool []string +} + +// this method will return a string of addr server from list server. +func (r *roundrobin) get() string { + r.Lock() + defer r.Unlock() + + if r.current >= len(r.pool) { + r.current %= len(r.pool) + } + + result := r.pool[r.current] + r.current++ + return result +} + +// BalancerForward Forward performs the given http request with round robin algorithm to server and fills the given http response. +// This method will return an fiber.Handler +func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler { + r := &roundrobin{ + current: 0, + pool: servers, + } + return func(c fiber.Ctx) error { + server := r.get() + if !strings.HasPrefix(server, "http") { + server = "http://" + server + } + c.Request().Header.Add("X-Real-IP", c.IP()) + return Do(c, server+c.OriginalURL(), clients...) + } +} diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index 4d86e9552f..b339398d1f 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "io" "net" - "net/http" "net/http/httptest" "strings" "testing" @@ -17,7 +16,7 @@ import ( "github.com/valyala/fasthttp" ) -func createProxyTestServer(handler fiber.Handler, t *testing.T) (*fiber.App, string) { +func createProxyTestServer(t *testing.T, handler fiber.Handler) (*fiber.App, string) { t.Helper() target := fiber.New() @@ -64,7 +63,7 @@ func Test_Proxy_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } @@ -73,11 +72,11 @@ func Test_Proxy_Next(t *testing.T) { func Test_Proxy(t *testing.T) { t.Parallel() - target, addr := createProxyTestServer( - func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusTeapot) }, t, - ) + target, addr := createProxyTestServer(t, func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusTeapot) + }) - resp, err := target.Test(httptest.NewRequest("GET", "/", nil), 2*time.Second) + resp, err := target.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) @@ -85,7 +84,7 @@ func Test_Proxy(t *testing.T) { app.Use(Balancer(Config{Servers: []string{addr}})) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Host = addr resp, err = app.Test(req) require.NoError(t, err) @@ -111,7 +110,7 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) { }) addr := ln.Addr().String() - clientTLSConf := &tls.Config{InsecureSkipVerify: true} + clientTLSConf := &tls.Config{InsecureSkipVerify: true} //nolint:gosec // We're in a test func, so this is fine // disable certificate verification in Balancer app.Use(Balancer(Config{ @@ -134,11 +133,11 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) { // 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 { + _, targetAddr := createProxyTestServer(t, func(c fiber.Ctx) error { return c.SendString("hello from target") - }, t) + }) proxyServerTLSConf, _, err := tlstest.GetTLSConfigs() require.NoError(t, err) @@ -176,13 +175,13 @@ func Test_Proxy_Forward(t *testing.T) { app := fiber.New() - _, addr := createProxyTestServer( - func(c fiber.Ctx) error { return c.SendString("forwarded") }, t, - ) + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { + return c.SendString("forwarded") + }) app.Use(Forward("http://" + addr)) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -210,7 +209,7 @@ func Test_Proxy_Forward_WithTlsConfig(t *testing.T) { }) addr := ln.Addr().String() - clientTLSConf := &tls.Config{InsecureSkipVerify: true} + clientTLSConf := &tls.Config{InsecureSkipVerify: true} //nolint:gosec // We're in a test func, so this is fine // disable certificate verification WithTlsConfig(clientTLSConf) @@ -233,9 +232,9 @@ func Test_Proxy_Forward_WithTlsConfig(t *testing.T) { func Test_Proxy_Modify_Response(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { return c.Status(500).SendString("not modified") - }, t) + }) app := fiber.New() app.Use(Balancer(Config{ @@ -246,7 +245,7 @@ func Test_Proxy_Modify_Response(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -259,10 +258,10 @@ func Test_Proxy_Modify_Response(t *testing.T) { func Test_Proxy_Modify_Request(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { b := c.Request().Body() return c.SendString(string(b)) - }, t) + }) app := fiber.New() app.Use(Balancer(Config{ @@ -273,7 +272,7 @@ func Test_Proxy_Modify_Request(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -286,10 +285,10 @@ func Test_Proxy_Modify_Request(t *testing.T) { func Test_Proxy_Timeout_Slow_Server(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { time.Sleep(2 * time.Second) return c.SendString("fiber is awesome") - }, t) + }) app := fiber.New() app.Use(Balancer(Config{ @@ -297,7 +296,7 @@ func Test_Proxy_Timeout_Slow_Server(t *testing.T) { Timeout: 3 * time.Second, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil), 5*time.Second) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 5*time.Second) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -310,10 +309,10 @@ func Test_Proxy_Timeout_Slow_Server(t *testing.T) { func Test_Proxy_With_Timeout(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { time.Sleep(1 * time.Second) return c.SendString("fiber is awesome") - }, t) + }) app := fiber.New() app.Use(Balancer(Config{ @@ -321,7 +320,7 @@ func Test_Proxy_With_Timeout(t *testing.T) { Timeout: 100 * time.Millisecond, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil), 2*time.Second) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second) require.NoError(t, err) require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) @@ -334,16 +333,16 @@ func Test_Proxy_With_Timeout(t *testing.T) { func Test_Proxy_Buffer_Size_Response(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { long := strings.Join(make([]string, 5000), "-") c.Set("Very-Long-Header", long) return c.SendString("ok") - }, t) + }) app := fiber.New() app.Use(Balancer(Config{Servers: []string{addr}})) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) @@ -353,7 +352,7 @@ func Test_Proxy_Buffer_Size_Response(t *testing.T) { ReadBufferSize: 1024 * 8, })) - resp, err = app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) } @@ -373,9 +372,9 @@ func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) { require.Equal(t, originalURL, c.OriginalURL()) return c.SendString("ok") }) - _, err1 := app.Test(httptest.NewRequest("GET", "/test", nil)) + _, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil)) // This test requires multiple requests due to zero allocation used in fiber - _, err2 := app.Test(httptest.NewRequest("GET", "/test", nil)) + _, err2 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil)) require.Nil(t, err1) require.Nil(t, err2) @@ -385,9 +384,9 @@ func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) { func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) { t.Parallel() - _, addr := createProxyTestServer(func(c fiber.Ctx) error { + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { return c.SendString("hello world") - }, t) + }) app := fiber.New() app.Get("/*", func(c fiber.Ctx) error { @@ -402,7 +401,7 @@ func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) { return nil }) - resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/http://"+addr, nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/http://"+addr, nil)) require.NoError(t, err) s, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -451,9 +450,8 @@ func Test_Proxy_Forward_Local_Client(t *testing.T) { 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) - }, + + Dial: fasthttp.Dial, })) go func() { require.Nil(t, app.Listener(ln, fiber.ListenConfig{ @@ -471,11 +469,11 @@ func Test_Proxy_Forward_Local_Client(t *testing.T) { func Test_ProxyBalancer_Custom_Client(t *testing.T) { t.Parallel() - target, addr := createProxyTestServer( - func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusTeapot) }, t, - ) + target, addr := createProxyTestServer(t, func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusTeapot) + }) - resp, err := target.Test(httptest.NewRequest("GET", "/", nil), 2*time.Second) + resp, err := target.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) @@ -492,9 +490,66 @@ func Test_ProxyBalancer_Custom_Client(t *testing.T) { Timeout: time.Second, }})) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Host = addr resp, err = app.Test(req) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } + +// go test -run Test_Proxy_Domain_Forward_Local +func Test_Proxy_Domain_Forward_Local(t *testing.T) { + t.Parallel() + ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") + require.NoError(t, err) + app := fiber.New() + + // target server + ln1, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") + require.NoError(t, err) + app1 := fiber.New() + + app1.Get("/test", func(c fiber.Ctx) error { + return c.SendString("test_local_client:" + c.Query("query_test")) + }) + + proxyAddr := ln.Addr().String() + targetAddr := ln1.Addr().String() + localDomain := strings.Replace(proxyAddr, "127.0.0.1", "localhost", 1) + app.Use(DomainForward(localDomain, "http://"+targetAddr, &fasthttp.Client{ + NoDefaultUserAgentHeader: true, + DisablePathNormalizing: true, + + Dial: fasthttp.Dial, + })) + + go func() { require.NoError(t, app.Listener(ln)) }() + go func() { require.NoError(t, app1.Listener(ln1)) }() + + code, body, errs := fiber.Get("http://" + localDomain + "/test?query_test=true").String() + require.Equal(t, 0, len(errs)) + require.Equal(t, fiber.StatusOK, code) + require.Equal(t, "test_local_client:true", body) +} + +// go test -run Test_Proxy_Balancer_Forward_Local +func Test_Proxy_Balancer_Forward_Local(t *testing.T) { + t.Parallel() + + app := fiber.New() + + _, addr := createProxyTestServer(t, func(c fiber.Ctx) error { + return c.SendString("forwarded") + }) + + app.Use(BalancerForward([]string{addr})) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + + b, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + require.Equal(t, string(b), "forwarded") +} diff --git a/middleware/recover/config.go b/middleware/recover/config.go index c16a106d1f..a857ae5b94 100644 --- a/middleware/recover/config.go +++ b/middleware/recover/config.go @@ -1,4 +1,4 @@ -package recover +package recover //nolint:predeclared // TODO: Rename to some non-builtin import ( "github.com/gofiber/fiber/v3" diff --git a/middleware/recover/recover.go b/middleware/recover/recover.go index 1f1e2aba60..4b9404c32a 100644 --- a/middleware/recover/recover.go +++ b/middleware/recover/recover.go @@ -1,4 +1,4 @@ -package recover +package recover //nolint:predeclared // TODO: Rename to some non-builtin import ( "fmt" @@ -8,8 +8,8 @@ import ( "github.com/gofiber/fiber/v3" ) -func defaultStackTraceHandler(_ fiber.Ctx, e any) { - _, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, debug.Stack())) +func defaultStackTraceHandler(_ fiber.Ctx, e interface{}) { + _, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, debug.Stack())) //nolint:errcheck // This will never fail } // New creates a new middleware handler @@ -18,7 +18,7 @@ func New(config ...Config) fiber.Handler { cfg := configDefault(config...) // Return new handler - return func(c fiber.Ctx) (err error) { + return func(c fiber.Ctx) (err error) { //nolint:nonamedreturns // Uses recover() to overwrite the error // Don't execute middleware if Next returns true if cfg.Next != nil && cfg.Next(c) { return c.Next() diff --git a/middleware/recover/recover_test.go b/middleware/recover/recover_test.go index b837a9d46d..411c231431 100644 --- a/middleware/recover/recover_test.go +++ b/middleware/recover/recover_test.go @@ -1,4 +1,4 @@ -package recover +package recover //nolint:predeclared // TODO: Rename to some non-builtin import ( "net/http/httptest" @@ -10,6 +10,7 @@ import ( // go test -run Test_Recover func Test_Recover(t *testing.T) { + t.Parallel() app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { require.Equal(t, "Hi, I'm an error!", err.Error()) @@ -23,13 +24,14 @@ func Test_Recover(t *testing.T) { panic("Hi, I'm an error!") }) - resp, err := app.Test(httptest.NewRequest("GET", "/panic", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/panic", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } // go test -run Test_Recover_Next func Test_Recover_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -37,12 +39,13 @@ func Test_Recover_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } func Test_Recover_EnableStackTrace(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ EnableStackTrace: true, @@ -52,7 +55,7 @@ func Test_Recover_EnableStackTrace(t *testing.T) { panic("Hi, I'm an error!") }) - resp, err := app.Test(httptest.NewRequest("GET", "/panic", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/panic", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode) } diff --git a/middleware/requestid/README.md b/middleware/requestid/README.md index 5ef9e6b30a..ee50c2e67d 100644 --- a/middleware/requestid/README.md +++ b/middleware/requestid/README.md @@ -64,13 +64,15 @@ type Config struct { ``` ### Default Config +The default config uses a fast UUID generator which will expose the number of +requests made to the server. To conceal this value for better privacy, use the +`utils.UUIDv4` generator. + ```go var ConfigDefault = Config{ Next: nil, Header: fiber.HeaderXRequestID, - Generator: func() string { - return utils.UUID() - }, - ContextKey: "requestid" + Generator: utils.UUID, + ContextKey: "requestid", } ``` diff --git a/middleware/requestid/config.go b/middleware/requestid/config.go index d29a62a4fe..aac5cb98be 100644 --- a/middleware/requestid/config.go +++ b/middleware/requestid/config.go @@ -30,6 +30,9 @@ type Config struct { } // ConfigDefault is the default config +// It uses a fast UUID generator which will expose the number of +// requests made to the server. To conceal this value for better +// privacy, use the "utils.UUIDv4" generator. var ConfigDefault = Config{ Next: nil, Header: fiber.HeaderXRequestID, diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go index f011373515..301fd44fc9 100644 --- a/middleware/requestid/requestid_test.go +++ b/middleware/requestid/requestid_test.go @@ -10,6 +10,7 @@ import ( // go test -run Test_RequestID func Test_RequestID(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New()) @@ -18,14 +19,14 @@ func Test_RequestID(t *testing.T) { return c.SendString("Hello, World 👋!") }) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) reqid := resp.Header.Get(fiber.HeaderXRequestID) require.Equal(t, 36, len(reqid)) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Add(fiber.HeaderXRequestID, reqid) resp, err = app.Test(req) @@ -36,6 +37,7 @@ func Test_RequestID(t *testing.T) { // go test -run Test_RequestID_Next func Test_RequestID_Next(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(New(Config{ Next: func(_ fiber.Ctx) bool { @@ -43,7 +45,7 @@ func Test_RequestID_Next(t *testing.T) { }, })) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, resp.Header.Get(fiber.HeaderXRequestID), "") require.Equal(t, fiber.StatusNotFound, resp.StatusCode) @@ -51,13 +53,14 @@ func Test_RequestID_Next(t *testing.T) { // go test -run Test_RequestID_Locals func Test_RequestID_Locals(t *testing.T) { - reqId := "ThisIsARequestId" + t.Parallel() + reqID := "ThisIsARequestId" ctxKey := "ThisIsAContextKey" app := fiber.New() app.Use(New(Config{ Generator: func() string { - return reqId + return reqID }, ContextKey: ctxKey, })) @@ -65,11 +68,11 @@ func Test_RequestID_Locals(t *testing.T) { var ctxVal string app.Use(func(c fiber.Ctx) error { - ctxVal = c.Locals(ctxKey).(string) + ctxVal = c.Locals(ctxKey).(string) //nolint:forcetypeassert,errcheck // We always store a string in here return c.Next() }) - _, err := app.Test(httptest.NewRequest("GET", "/", nil)) + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) - require.Equal(t, reqId, ctxVal) + require.Equal(t, reqID, ctxVal) } diff --git a/middleware/session/README.md b/middleware/session/README.md index 085e621cd1..59b324e2c2 100644 --- a/middleware/session/README.md +++ b/middleware/session/README.md @@ -32,7 +32,7 @@ func (s *Session) Save() error func (s *Session) Fresh() bool func (s *Session) ID() string func (s *Session) Keys() []string -func (s *Session) SetExpiry(time.Duration) +func (s *Session) SetExpiry(time.Duration) ``` **⚠ _Storing `any` values are limited to built-ins Go types_** @@ -148,7 +148,7 @@ type Config struct { // Optional. Default value utils.UUID KeyGenerator func() string - // Deprecated, please use KeyLookup + // Deprecated: Please use KeyLookup CookieName string // Source defines where to obtain the session id diff --git a/middleware/session/config.go b/middleware/session/config.go index b304001ddc..8d7a5e980c 100644 --- a/middleware/session/config.go +++ b/middleware/session/config.go @@ -94,7 +94,8 @@ func configDefault(config ...Config) Config { } selectors := strings.Split(cfg.KeyLookup, ":") - if len(selectors) != 2 { + const numSelectors = 2 + if len(selectors) != numSelectors { panic("[session] KeyLookup must in the form of :") } switch Source(selectors[0]) { diff --git a/middleware/session/data.go b/middleware/session/data.go index baf502100d..d80c767ea1 100644 --- a/middleware/session/data.go +++ b/middleware/session/data.go @@ -4,6 +4,8 @@ import ( "sync" ) +// go:generate msgp +// msgp -file="data.go" -o="data_msgp.go" -tests=false -unexported type data struct { sync.RWMutex Data map[string]any @@ -18,7 +20,7 @@ var dataPool = sync.Pool{ } func acquireData() *data { - return dataPool.Get().(*data) + return dataPool.Get().(*data) //nolint:forcetypeassert // We store nothing else in the pool } func (d *data) Reset() { diff --git a/middleware/session/session.go b/middleware/session/session.go index ce145e5637..b537c7408b 100644 --- a/middleware/session/session.go +++ b/middleware/session/session.go @@ -3,6 +3,7 @@ package session import ( "bytes" "encoding/gob" + "fmt" "sync" "time" @@ -28,7 +29,7 @@ var sessionPool = sync.Pool{ } func acquireSession() *Session { - s := sessionPool.Get().(*Session) + s := sessionPool.Get().(*Session) //nolint:forcetypeassert,errcheck // We store nothing else in the pool if s.data == nil { s.data = acquireData() } @@ -153,7 +154,7 @@ func (s *Session) Save() error { encCache := gob.NewEncoder(s.byteBuffer) err := encCache.Encode(&s.data.Data) if err != nil { - return err + return fmt.Errorf("failed to encode data: %w", err) } // copy the data in buffer diff --git a/middleware/session/session_test.go b/middleware/session/session_test.go index 891abb7f3c..652b7dd658 100644 --- a/middleware/session/session_test.go +++ b/middleware/session/session_test.go @@ -91,6 +91,8 @@ func Test_Session(t *testing.T) { } // go test -run Test_Session_Types +// +//nolint:forcetypeassert // TODO: Do not force-type assert func Test_Session_Types(t *testing.T) { t.Parallel() @@ -123,25 +125,27 @@ func Test_Session_Types(t *testing.T) { Name: "John", } // set value - var vbool = true - var vstring = "str" - var vint = 13 - var vint8 int8 = 13 - var vint16 int16 = 13 - var vint32 int32 = 13 - var vint64 int64 = 13 - var vuint uint = 13 - var vuint8 uint8 = 13 - var vuint16 uint16 = 13 - var vuint32 uint32 = 13 - var vuint64 uint64 = 13 - var vuintptr uintptr = 13 - var vbyte byte = 'k' - var vrune rune = 'k' - var vfloat32 float32 = 13 - var vfloat64 float64 = 13 - var vcomplex64 complex64 = 13 - var vcomplex128 complex128 = 13 + var ( + vbool = true + vstring = "str" + vint = 13 + vint8 int8 = 13 + vint16 int16 = 13 + vint32 int32 = 13 + vint64 int64 = 13 + vuint uint = 13 + vuint8 uint8 = 13 + vuint16 uint16 = 13 + vuint32 uint32 = 13 + vuint64 uint64 = 13 + vuintptr uintptr = 13 + vbyte byte = 'k' + vrune = 'k' + vfloat32 float32 = 13 + vfloat64 float64 = 13 + vcomplex64 complex64 = 13 + vcomplex128 complex128 = 13 + ) sess.Set("vuser", vuser) sess.Set("vbool", vbool) sess.Set("vstring", vstring) @@ -207,7 +211,8 @@ func Test_Session_Store_Reset(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) // make sure its new require.True(t, sess.Fresh()) // set value & save @@ -219,7 +224,8 @@ func Test_Session_Store_Reset(t *testing.T) { require.NoError(t, store.Reset()) // make sure the session is recreated - sess, _ = store.Get(ctx) + sess, err = store.Get(ctx) + require.NoError(t, err) require.True(t, sess.Fresh()) require.Nil(t, sess.Get("hello")) } @@ -237,12 +243,13 @@ func Test_Session_Save(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) // set value sess.Set("name", "john") // save session - err := sess.Save() + err = sess.Save() require.NoError(t, err) }) @@ -257,12 +264,13 @@ func Test_Session_Save(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) // set value sess.Set("name", "john") // save session - err := sess.Save() + err = sess.Save() 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))) @@ -273,6 +281,7 @@ func Test_Session_Save_Expiration(t *testing.T) { t.Parallel() t.Run("save to cookie", func(t *testing.T) { + t.Parallel() // session store store := New() // fiber instance @@ -281,7 +290,9 @@ func Test_Session_Save_Expiration(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) + // set value sess.Set("name", "john") @@ -289,18 +300,20 @@ func Test_Session_Save_Expiration(t *testing.T) { sess.SetExpiry(time.Second * 5) // save session - err := sess.Save() + err = sess.Save() require.NoError(t, err) // here you need to get the old session yet - sess, _ = store.Get(ctx) + sess, err = store.Get(ctx) + require.NoError(t, err) 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) + sess, err = store.Get(ctx) + require.NoError(t, err) require.Nil(t, sess.Get("name")) }) } @@ -310,6 +323,7 @@ func Test_Session_Reset(t *testing.T) { t.Parallel() t.Run("reset from cookie", func(t *testing.T) { + t.Parallel() // session store store := New() // fiber instance @@ -318,7 +332,8 @@ func Test_Session_Reset(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) sess.Set("name", "fenny") require.NoError(t, sess.Destroy()) @@ -327,6 +342,7 @@ func Test_Session_Reset(t *testing.T) { }) t.Run("reset from header", func(t *testing.T) { + t.Parallel() // session store store := New(Config{ KeyLookup: "header:session_id", @@ -337,14 +353,15 @@ func Test_Session_Reset(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) // set value & save sess.Set("name", "fenny") require.NoError(t, sess.Save()) sess, _ = store.Get(ctx) - err := sess.Destroy() + err = sess.Destroy() require.NoError(t, err) require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) @@ -374,7 +391,8 @@ func Test_Session_Cookie(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) require.NoError(t, sess.Save()) // cookie should be set on Save ( even if empty data ) @@ -391,12 +409,14 @@ func Test_Session_Cookie_In_Response(t *testing.T) { ctx := app.NewCtx(&fasthttp.RequestCtx{}) // get session - sess, _ := store.Get(ctx) + sess, err := store.Get(ctx) + require.NoError(t, err) sess.Set("id", "1") require.True(t, sess.Fresh()) require.NoError(t, sess.Save()) - sess, _ = store.Get(ctx) + sess, err = store.Get(ctx) + require.NoError(t, err) sess.Set("name", "john") require.True(t, sess.Fresh()) @@ -434,6 +454,7 @@ func Test_Session_Deletes_Single_Key(t *testing.T) { // go test -run Test_Session_Regenerate // Regression: https://github.com/gofiber/fiber/issues/1395 func Test_Session_Regenerate(t *testing.T) { + t.Parallel() // fiber instance app := fiber.New() t.Run("set fresh to be true when regenerating a session", func(t *testing.T) { @@ -484,7 +505,7 @@ func Benchmark_Session(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - sess, _ := store.Get(c) + sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") err = sess.Save() } @@ -499,7 +520,7 @@ func Benchmark_Session(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - sess, _ := store.Get(c) + sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") err = sess.Save() } diff --git a/middleware/session/store.go b/middleware/session/store.go index 77537abe92..e8f6990966 100644 --- a/middleware/session/store.go +++ b/middleware/session/store.go @@ -2,6 +2,7 @@ package session import ( "encoding/gob" + "fmt" "sync" "github.com/gofiber/fiber/v3" @@ -31,7 +32,7 @@ func New(config ...Config) *Store { // RegisterType will allow you to encode/decode custom types // into any Storage provider -func (s *Store) RegisterType(i any) { +func (*Store) RegisterType(i any) { gob.Register(i) } @@ -70,11 +71,11 @@ func (s *Store) Get(c fiber.Ctx) (*Session, error) { if raw != nil && err == nil { mux.Lock() defer mux.Unlock() - _, _ = sess.byteBuffer.Write(raw) + _, _ = sess.byteBuffer.Write(raw) //nolint:errcheck // This will never fail encCache := gob.NewDecoder(sess.byteBuffer) err := encCache.Decode(&sess.data.Data) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to decode session data: %w", err) } } else if err != nil { return nil, err diff --git a/middleware/session/store_test.go b/middleware/session/store_test.go index b579e59379..bb93ababc5 100644 --- a/middleware/session/store_test.go +++ b/middleware/session/store_test.go @@ -11,12 +11,14 @@ import ( // go test -run TestStore_getSessionID func TestStore_getSessionID(t *testing.T) { + t.Parallel() expectedID := "test-session-id" // fiber instance app := fiber.New() t.Run("from cookie", func(t *testing.T) { + t.Parallel() // session store store := New() // fiber context @@ -29,6 +31,7 @@ func TestStore_getSessionID(t *testing.T) { }) t.Run("from header", func(t *testing.T) { + t.Parallel() // session store store := New(Config{ KeyLookup: "header:session_id", @@ -43,6 +46,7 @@ func TestStore_getSessionID(t *testing.T) { }) t.Run("from url query", func(t *testing.T) { + t.Parallel() // session store store := New(Config{ KeyLookup: "query:session_id", @@ -60,10 +64,12 @@ func TestStore_getSessionID(t *testing.T) { // go test -run TestStore_Get // Regression: https://github.com/gofiber/fiber/issues/1408 func TestStore_Get(t *testing.T) { + t.Parallel() unexpectedID := "test-session-id" // fiber instance app := fiber.New() t.Run("session should persisted even session is invalid", func(t *testing.T) { + t.Parallel() // session store store := New() // fiber context diff --git a/middleware/skip/skip_test.go b/middleware/skip/skip_test.go index 4373e18e37..cfa95f8cbc 100644 --- a/middleware/skip/skip_test.go +++ b/middleware/skip/skip_test.go @@ -11,36 +11,39 @@ import ( // go test -run Test_Skip func Test_Skip(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(skip.New(errTeapotHandler, func(fiber.Ctx) bool { return true })) app.Get("/", helloWorldHandler) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode) } // go test -run Test_SkipFalse func Test_SkipFalse(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(skip.New(errTeapotHandler, func(fiber.Ctx) bool { return false })) app.Get("/", helloWorldHandler) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } // go test -run Test_SkipNilFunc func Test_SkipNilFunc(t *testing.T) { + t.Parallel() app := fiber.New() app.Use(skip.New(errTeapotHandler, nil)) app.Get("/", helloWorldHandler) - resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) require.NoError(t, err) require.Equal(t, fiber.StatusTeapot, resp.StatusCode) } diff --git a/middleware/timeout/timeout_test.go b/middleware/timeout/timeout_test.go index 4d5d4aa1aa..a410b4358d 100644 --- a/middleware/timeout/timeout_test.go +++ b/middleware/timeout/timeout_test.go @@ -14,10 +14,12 @@ import ( // go test -run Test_Timeout func Test_Timeout(t *testing.T) { + t.Parallel() // fiber instance app := fiber.New() h := New(func(c fiber.Ctx) error { - sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms") + require.NoError(t, err) if err := sleepWithContext(c.UserContext(), sleepTime, context.DeadlineExceeded); err != nil { return fmt.Errorf("%w: l2 wrap", fmt.Errorf("%w: l1 wrap ", err)) } @@ -25,13 +27,13 @@ func Test_Timeout(t *testing.T) { }, 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)") + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test/"+timeoutStr, nil)) + require.NoError(t, 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)") + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test/"+timeoutStr, nil)) + require.NoError(t, err, "app.Test(req)") require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") } testTimeout("300") @@ -44,10 +46,12 @@ var ErrFooTimeOut = errors.New("foo context canceled") // go test -run Test_TimeoutWithCustomError func Test_TimeoutWithCustomError(t *testing.T) { + t.Parallel() // fiber instance app := fiber.New() h := New(func(c fiber.Ctx) error { - sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") + sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms") + require.NoError(t, err) if err := sleepWithContext(c.UserContext(), sleepTime, ErrFooTimeOut); err != nil { return fmt.Errorf("%w: execution error", err) } @@ -55,13 +59,13 @@ func Test_TimeoutWithCustomError(t *testing.T) { }, 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)") + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test/"+timeoutStr, nil)) + require.NoError(t, 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)") + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test/"+timeoutStr, nil)) + require.NoError(t, err, "app.Test(req)") require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code") } testTimeout("300") diff --git a/mount.go b/mount.go index 83b9229aa1..457875784c 100644 --- a/mount.go +++ b/mount.go @@ -122,7 +122,6 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { if len(subApp.mountFields.appList) > 1 { app.appendSubAppLists(subApp.mountFields.appList, prefix) } - } } diff --git a/mount_test.go b/mount_test.go index 7daebc3dd1..c374c350e6 100644 --- a/mount_test.go +++ b/mount_test.go @@ -2,6 +2,7 @@ // 🤖 Github Repository: https://github.com/gofiber/fiber // 📌 API Documentation: https://docs.gofiber.io +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package fiber import ( @@ -15,6 +16,7 @@ import ( // go test -run Test_App_Mount func Test_App_Mount(t *testing.T) { + t.Parallel() micro := New() micro.Get("/doe", func(c Ctx) error { return c.SendStatus(StatusOK) @@ -29,6 +31,7 @@ func Test_App_Mount(t *testing.T) { } func Test_App_Mount_RootPath_Nested(t *testing.T) { + t.Parallel() app := New() dynamic := New() apiserver := New() @@ -49,6 +52,7 @@ func Test_App_Mount_RootPath_Nested(t *testing.T) { // go test -run Test_App_Mount_Nested func Test_App_Mount_Nested(t *testing.T) { + t.Parallel() app := New() one := New() two := New() @@ -87,6 +91,7 @@ func Test_App_Mount_Nested(t *testing.T) { // go test -run Test_App_MountPath func Test_App_MountPath(t *testing.T) { + t.Parallel() app := New() one := New() two := New() @@ -103,6 +108,7 @@ func Test_App_MountPath(t *testing.T) { } func Test_App_ErrorHandler_GroupMount(t *testing.T) { + t.Parallel() micro := New(Config{ ErrorHandler: func(c Ctx, err error) error { require.Equal(t, "0: GET error", err.Error()) @@ -122,6 +128,7 @@ func Test_App_ErrorHandler_GroupMount(t *testing.T) { } func Test_App_ErrorHandler_GroupMountRootLevel(t *testing.T) { + t.Parallel() micro := New(Config{ ErrorHandler: func(c Ctx, err error) error { require.Equal(t, "0: GET error", err.Error()) @@ -142,6 +149,7 @@ func Test_App_ErrorHandler_GroupMountRootLevel(t *testing.T) { // go test -run Test_App_Group_Mount func Test_App_Group_Mount(t *testing.T) { + t.Parallel() micro := New() micro.Get("/doe", func(c Ctx) error { return c.SendStatus(StatusOK) @@ -158,6 +166,7 @@ func Test_App_Group_Mount(t *testing.T) { } func Test_App_UseParentErrorHandler(t *testing.T) { + t.Parallel() app := New(Config{ ErrorHandler: func(ctx Ctx, err error) error { return ctx.Status(500).SendString("hi, i'm a custom error") @@ -176,6 +185,7 @@ func Test_App_UseParentErrorHandler(t *testing.T) { } func Test_App_UseMountedErrorHandler(t *testing.T) { + t.Parallel() app := New() fiber := New(Config{ @@ -194,6 +204,7 @@ func Test_App_UseMountedErrorHandler(t *testing.T) { } func Test_App_UseMountedErrorHandlerRootLevel(t *testing.T) { + t.Parallel() app := New() fiber := New(Config{ @@ -212,6 +223,7 @@ func Test_App_UseMountedErrorHandlerRootLevel(t *testing.T) { } func Test_App_UseMountedErrorHandlerForBestPrefixMatch(t *testing.T) { + t.Parallel() app := New() tsf := func(c Ctx, err error) error { diff --git a/path.go b/path.go index 8936f90427..c7a41dc994 100644 --- a/path.go +++ b/path.go @@ -13,6 +13,7 @@ import ( "time" "unicode" + "github.com/gofiber/utils/v2" "github.com/google/uuid" ) @@ -93,7 +94,7 @@ var ( routeDelimiter = []byte{slashDelimiter, '-', '.'} // list of greedy parameters greedyParameters = []byte{wildcardParam, plusParam} - // list of chars for the parameter recognising + // list of chars for the parameter recognizing parameterStartChars = []byte{wildcardParam, plusParam, paramStarterChar} // list of chars of delimiters and the starting parameter name char parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...) @@ -113,6 +114,65 @@ var ( parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator} ) +// RoutePatternMatch checks if a given path matches a Fiber route pattern. +func RoutePatternMatch(path, pattern string, cfg ...Config) bool { + // See logic in (*Route).match and (*App).register + var ctxParams [maxParams]string + + config := Config{} + if len(cfg) > 0 { + config = cfg[0] + } + + if path == "" { + path = "/" + } + + // Cannot have an empty pattern + if pattern == "" { + pattern = "/" + } + // Pattern always start with a '/' + if pattern[0] != '/' { + pattern = "/" + pattern + } + + patternPretty := pattern + + // Case sensitive routing, all to lowercase + if !config.CaseSensitive { + patternPretty = utils.ToLower(patternPretty) + path = utils.ToLower(path) + } + // Strict routing, remove trailing slashes + if !config.StrictRouting && len(patternPretty) > 1 { + patternPretty = strings.TrimRight(patternPretty, "/") + } + + parser := parseRoute(patternPretty) + + if patternPretty == "/" && path == "/" { + return true + // '*' wildcard matches any path + } else if patternPretty == "/*" { + return true + } + + // Does this route have parameters + if len(parser.params) > 0 { + if match := parser.getMatch(path, path, &ctxParams, false); match { + return true + } + } + // Check for a simple match + patternPretty = RemoveEscapeChar(patternPretty) + if len(patternPretty) == len(path) && patternPretty == path { + return true + } + // No match + return false +} + // parseRoute analyzes the route and divides it into segments for constant areas and parameters, // this information is needed later when assigning the requests to the declared routes func parseRoute(pattern string) routeParser { @@ -209,7 +269,7 @@ func findNextParamPosition(pattern string) int { } // analyseConstantPart find the end of the constant part and create the route segment -func (routeParser *routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) { +func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) { // handle the constant part processedPart := pattern if nextParamPosition != -1 { @@ -238,11 +298,12 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r parameterConstraintStart := -1 parameterConstraintEnd := -1 // handle wildcard end - if isWildCard || isPlusParam { + switch { + case isWildCard, isPlusParam: parameterEndPosition = 0 - } else if parameterEndPosition == -1 { + case parameterEndPosition == -1: parameterEndPosition = len(pattern) - 1 - } else if !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars) { + case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars): parameterEndPosition++ } @@ -271,15 +332,15 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r // Assign constraint if start != -1 && end != -1 { constraint := &Constraint{ - ID: getParamConstraintType(c[:start]), - Data: splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)), + ID: getParamConstraintType(c[:start]), } // remove escapes from data if constraint.ID != regexConstraint { + constraint.Data = splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)) if len(constraint.Data) == 1 { constraint.Data[0] = RemoveEscapeChar(constraint.Data[0]) - } else if len(constraint.Data) == 2 { + } else if len(constraint.Data) == 2 { // This is fine, we simply expect two parts constraint.Data[0] = RemoveEscapeChar(constraint.Data[0]) constraint.Data[1] = RemoveEscapeChar(constraint.Data[1]) } @@ -287,6 +348,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r // Precompile regex if has regex constraint if constraint.ID == regexConstraint { + constraint.Data = []string{c[start+1 : end]} constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0]) } @@ -414,7 +476,7 @@ func splitNonEscaped(s, sep string) []string { } // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions -func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { +func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here var i, paramsIterator, partLen int for _, segment := range routeParser.segs { partLen = len(detectionPath) @@ -578,9 +640,9 @@ func getParamConstraintType(constraintPart string) TypeConstraint { default: return noConstraint } - } +//nolint:errcheck // TODO: Properly check _all_ errors in here, log them & immediately return func (c *Constraint) CheckConstraint(param string) bool { var err error var num int @@ -603,6 +665,8 @@ func (c *Constraint) CheckConstraint(param string) bool { // check constraints switch c.ID { + case noConstraint: + // Nothing to check case intConstraint: _, err = strconv.Atoi(param) case boolConstraint: diff --git a/path_test.go b/path_test.go index b9ee575c86..26b11cffdb 100644 --- a/path_test.go +++ b/path_test.go @@ -6,6 +6,7 @@ package fiber import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/require" @@ -13,6 +14,7 @@ import ( // go test -race -run Test_Path_parseRoute func Test_Path_parseRoute(t *testing.T) { + t.Parallel() var rp routeParser rp = parseRoute("/shop/product/::filter/color::color/size::size") @@ -527,6 +529,12 @@ func Test_Path_matchParams(t *testing.T) { {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/12", params: nil, match: false}, + {url: "/api/v1/xy", params: nil, match: false}, + {url: "/api/v1/test", params: []string{"test"}, match: true}, + {url: "/api/v1/" + strings.Repeat("a", 64), 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}, @@ -606,6 +614,459 @@ func Test_Path_matchParams(t *testing.T) { }) } +// go test -race -run Test_RoutePatternMatch +func Test_RoutePatternMatch(t *testing.T) { + t.Parallel() + type testparams struct { + url string + match bool + } + testCase := func(pattern string, cases []testparams) { + for _, c := range cases { + match := RoutePatternMatch(c.url, pattern) + require.Equal(t, c.match, match, fmt.Sprintf("route: '%s', url: '%s'", pattern, c.url)) + } + } + testCase("/api/v1/:param/*", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/", match: true}, + {url: "/api/v1/entity/1", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/v1/", match: false}, + }) + testCase("/api/v1/:param/+", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/entity/", match: false}, + {url: "/api/v1/entity/1", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/v1/", match: false}, + }) + testCase("/api/v1/:param?", []testparams{ + {url: "/api/v1", match: true}, + {url: "/api/v1/", match: true}, + {url: "/api/v1/optional", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/xyz", match: false}, + }) + testCase("/v1/some/resource/name\\:customVerb", []testparams{ + {url: "/v1/some/resource/name:customVerb", match: true}, + {url: "/v1/some/resource/name:test", match: false}, + }) + testCase("/v1/some/resource/:name\\:customVerb", []testparams{ + {url: "/v1/some/resource/test:customVerb", match: true}, + {url: "/v1/some/resource/test:test", match: false}, + }) + testCase("/v1/some/resource/name\\\\:customVerb?\\?/:param/*", []testparams{ + {url: "/v1/some/resource/name:customVerb??/test/optionalWildCard/character", match: true}, + {url: "/v1/some/resource/name:customVerb??/test", match: true}, + }) + testCase("/api/v1/*", []testparams{ + {url: "/api/v1", match: true}, + {url: "/api/v1/", match: true}, + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/1/2", match: true}, + {url: "/api/v1/Entity/1/2", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/abc", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/8728382", match: false}, + {url: "/api/v1", match: false}, + {url: "/api/v1/", match: false}, + }) + testCase("/api/v1/:param-:param2", []testparams{ + {url: "/api/v1/entity-entity2", match: true}, + {url: "/api/v1/entity/8728382", match: false}, + {url: "/api/v1/entity-8728382", match: true}, + {url: "/api/v1", match: false}, + {url: "/api/v1/", match: false}, + }) + testCase("/api/v1/:filename.:extension", []testparams{ + {url: "/api/v1/test.pdf", match: true}, + {url: "/api/v1/test/pdf", match: false}, + {url: "/api/v1/test-pdf", match: false}, + {url: "/api/v1/test_pdf", match: false}, + {url: "/api/v1", match: false}, + {url: "/api/v1/", match: false}, + }) + testCase("/api/v1/const", []testparams{ + {url: "/api/v1/const", match: true}, + {url: "/api/v1", match: false}, + {url: "/api/v1/", match: false}, + {url: "/api/v1/something", match: false}, + }) + testCase("/api/:param/fixedEnd", []testparams{ + {url: "/api/abc/fixedEnd", match: true}, + {url: "/api/abc/def/fixedEnd", match: false}, + }) + testCase("/shop/product/::filter/color::color/size::size", []testparams{ + {url: "/shop/product/:test/color:blue/size:xs", match: true}, + {url: "/shop/product/test/color:blue/size:xs", match: false}, + }) + testCase("/::param?", []testparams{ + {url: "/:hello", match: true}, + {url: "/:", match: true}, + {url: "/", match: false}, + }) + // successive parameters, each take one character and the last parameter gets everything + testCase("/test:sign:param", []testparams{ + {url: "/test-abc", match: true}, + {url: "/test", match: false}, + }) + // optional parameters are not greedy + testCase("/:param1:param2?:param3", []testparams{ + {url: "/abbbc", match: true}, + // {url: "/ac", params: []string{"a", "", "c"}, match: true}, // TODO: fix it + {url: "/test", match: true}, + }) + testCase("/test:optional?:mandatory", []testparams{ + // {url: "/testo", params: []string{"", "o"}, match: true}, // TODO: fix it + {url: "/testoaaa", match: true}, + {url: "/test", match: false}, + }) + testCase("/test:optional?:optional2?", []testparams{ + {url: "/testo", match: true}, + {url: "/testoaaa", match: true}, + {url: "/test", match: true}, + {url: "/tes", match: false}, + }) + testCase("/foo:param?bar", []testparams{ + {url: "/foofaselbar", match: true}, + {url: "/foobar", match: true}, + {url: "/fooba", match: false}, + {url: "/fobar", match: false}, + }) + testCase("/foo*bar", []testparams{ + {url: "/foofaselbar", match: true}, + {url: "/foobar", match: true}, + {url: "/", match: false}, + }) + testCase("/foo+bar", []testparams{ + {url: "/foofaselbar", match: true}, + {url: "/foobar", match: false}, + {url: "/", match: false}, + }) + testCase("/a*cde*g/", []testparams{ + {url: "/abbbcdefffg", match: true}, + {url: "/acdeg", match: true}, + {url: "/", match: false}, + }) + testCase("/*v1*/proxy", []testparams{ + {url: "/customer/v1/cart/proxy", match: true}, + {url: "/v1/proxy", match: true}, + {url: "/v1/", match: false}, + }) + // successive wildcard -> first wildcard is greedy + testCase("/foo***bar", []testparams{ + {url: "/foo*abar", match: true}, + {url: "/foo*bar", match: true}, + {url: "/foobar", match: true}, + {url: "/fooba", match: false}, + }) + // chars in front of an parameter + testCase("/name::name", []testparams{ + {url: "/name:john", match: true}, + }) + testCase("/@:name", []testparams{ + {url: "/@john", match: true}, + }) + testCase("/-:name", []testparams{ + {url: "/-john", match: true}, + }) + testCase("/.:name", []testparams{ + {url: "/.john", match: true}, + }) + testCase("/api/v1/:param/abc/*", []testparams{ + {url: "/api/v1/well/abc/wildcard", match: true}, + {url: "/api/v1/well/abc/", match: true}, + {url: "/api/v1/well/abc", match: true}, + {url: "/api/v1/well/ttt", match: false}, + }) + testCase("/api/:day/:month?/:year?", []testparams{ + {url: "/api/1", match: true}, + {url: "/api/1/", match: true}, + {url: "/api/1//", match: true}, + {url: "/api/1/-/", match: true}, + {url: "/api/1-", match: true}, + {url: "/api/1.", match: true}, + {url: "/api/1/2", match: true}, + {url: "/api/1/2/3", match: true}, + {url: "/api/", match: false}, + }) + testCase("/api/:day.:month?.:year?", []testparams{ + {url: "/api/1", match: false}, + {url: "/api/1/", match: false}, + {url: "/api/1.", match: false}, + {url: "/api/1..", match: true}, + {url: "/api/1.2", match: false}, + {url: "/api/1.2.", match: true}, + {url: "/api/1.2.3", match: true}, + {url: "/api/", match: false}, + }) + testCase("/api/:day-:month?-:year?", []testparams{ + {url: "/api/1", match: false}, + {url: "/api/1/", match: false}, + {url: "/api/1-", match: false}, + {url: "/api/1--", match: true}, + {url: "/api/1-/", match: false}, + // {url: "/api/1-/-", params: nil, match: false}, // TODO: fix this part + {url: "/api/1-2", match: false}, + {url: "/api/1-2-", match: true}, + {url: "/api/1-2-3", match: true}, + {url: "/api/", match: false}, + }) + testCase("/api/*", []testparams{ + {url: "/api/", match: true}, + {url: "/api/joker", match: true}, + {url: "/api", match: true}, + {url: "/api/v1/entity", match: true}, + {url: "/api2/v1/entity", match: false}, + {url: "/api_ignore/v1/entity", match: false}, + }) + testCase("/", []testparams{ + {url: "/api", match: false}, + {url: "", match: true}, + {url: "/", match: true}, + }) + testCase("/config/abc.json", []testparams{ + {url: "/config/abc.json", match: true}, + {url: "config/abc.json", match: false}, + {url: "/config/efg.json", match: false}, + {url: "/config", match: false}, + }) + testCase("/config/*.json", []testparams{ + {url: "/config/abc.json", match: true}, + {url: "/config/efg.json", match: true}, + {url: "/config/.json", match: true}, + {url: "/config/efg.csv", match: false}, + {url: "config/abc.json", match: false}, + {url: "/config", match: false}, + }) + testCase("/config/+.json", []testparams{ + {url: "/config/abc.json", match: true}, + {url: "/config/.json", match: false}, + {url: "/config/efg.json", match: true}, + {url: "/config/efg.csv", match: false}, + {url: "config/abc.json", match: false}, + {url: "/config", match: false}, + }) + testCase("/xyz", []testparams{ + {url: "xyz", match: false}, + {url: "xyz/", match: false}, + }) + testCase("/api/*/:param?", []testparams{ + {url: "/api/", match: true}, + {url: "/api/joker", match: true}, + {url: "/api/joker/batman", match: true}, + {url: "/api/joker//batman", match: true}, + {url: "/api/joker/batman/robin", match: true}, + {url: "/api/joker/batman/robin/1", match: true}, + {url: "/api/joker/batman/robin/1/", match: true}, + {url: "/api/joker-batman/robin/1", match: true}, + {url: "/api/joker-batman-robin/1", match: true}, + {url: "/api/joker-batman-robin-1", match: true}, + {url: "/api", match: true}, + }) + testCase("/api/*/:param", []testparams{ + {url: "/api/test/abc", match: true}, + {url: "/api/joker/batman", match: true}, + {url: "/api/joker/batman/robin", match: true}, + {url: "/api/joker/batman/robin/1", match: true}, + {url: "/api/joker/batman-robin/1", match: true}, + {url: "/api/joker-batman-robin-1", match: false}, + {url: "/api", match: false}, + }) + testCase("/api/+/:param", []testparams{ + {url: "/api/test/abc", match: true}, + {url: "/api/joker/batman/robin/1", match: true}, + {url: "/api/joker", match: false}, + {url: "/api", match: false}, + }) + testCase("/api/*/:param/:param2", []testparams{ + {url: "/api/test/abc/1", match: true}, + {url: "/api/joker/batman", match: false}, + {url: "/api/joker/batman-robin/1", match: true}, + {url: "/api/joker-batman-robin-1", match: false}, + {url: "/api/test/abc", match: false}, + {url: "/api/joker/batman/robin", match: true}, + {url: "/api/joker/batman/robin/1", match: true}, + {url: "/api/joker/batman/robin/1/2", match: true}, + {url: "/api", match: false}, + {url: "/api/:test", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/true", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/8728382.5", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/#!?", match: false}, + {url: "/api/v1/8728382", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/123", match: false}, + {url: "/api/v1/12345", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/ent", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/123", match: false}, + {url: "/api/v1/12345", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/ent", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", match: false}, + {url: "/api/v1/en", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", match: false}, + {url: "/api/v1/en", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/1", match: false}, + {url: "/api/v1/5", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/1", match: true}, + {url: "/api/v1/5", match: true}, + {url: "/api/v1/15", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/9", match: true}, + {url: "/api/v1/5", match: true}, + {url: "/api/v1/15", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/2005-11-01", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/15", match: false}, + {url: "/api/v1/peach", match: true}, + {url: "/api/v1/p34ch", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/15", match: false}, + {url: "/api/v1/2022-08-27", match: true}, + {url: "/api/v1/2022/08-27", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/87283827683", match: true}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/true", match: true}, + }) + testCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/1200", match: true}, + {url: "/api/v1/true", match: false}, + }) + testCase("/api/v1/:lang/videos/:page", []testparams{ + {url: "/api/v1/try/videos/200", match: false}, + {url: "/api/v1/tr/videos/1800", match: false}, + {url: "/api/v1/tr/videos/100", match: true}, + {url: "/api/v1/e/videos/10", match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: false}, + {url: "/api/v1/tr/1800", match: false}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: true}, + {url: "/api/v1/tr/1800", match: false}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + testCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: false}, + {url: "/api/v1/tr/1800", match: true}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + testCase("/api/v1/:date/:regex", []testparams{ + {url: "/api/v1/2005-11-01/a", match: false}, + {url: "/api/v1/2005-1101/paach", match: false}, + {url: "/api/v1/2005-11-01/peach", match: true}, + }) +} + func Test_Utils_GetTrimmedParam(t *testing.T) { t.Parallel() res := GetTrimmedParam("") @@ -659,7 +1120,6 @@ func Benchmark_Path_matchParams(t *testing.B) { 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)) } }) - } } benchCase("/api/:param/fixedEnd", []testparams{ @@ -865,3 +1325,225 @@ func Benchmark_Path_matchParams(t *testing.B) { {url: "/api/v1/", params: []string{""}, match: true}, }) } + +// go test -race -run Test_RoutePatternMatch +func Benchmark_RoutePatternMatch(t *testing.B) { + type testparams struct { + url string + match bool + } + benchCase := func(pattern string, cases []testparams) { + for _, c := range cases { + var matchRes bool + state := "match" + if !c.match { + state = "not match" + } + t.Run(pattern+" | "+state+" | "+c.url, func(b *testing.B) { + for i := 0; i <= b.N; i++ { + if match := RoutePatternMatch(c.url, pattern); match { + // Get params from the original path + matchRes = true + } + } + require.Equal(t, c.match, matchRes, fmt.Sprintf("route: '%s', url: '%s'", pattern, c.url)) + }) + } + } + benchCase("/api/:param/fixedEnd", []testparams{ + {url: "/api/abc/fixedEnd", match: true}, + {url: "/api/abc/def/fixedEnd", match: false}, + }) + benchCase("/api/v1/:param/*", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/", match: true}, + {url: "/api/v1/entity/1", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/v1/", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/8728382", match: false}, + {url: "/api/v1", match: false}, + {url: "/api/v1/", match: false}, + }) + benchCase("/api/v1", []testparams{ + {url: "/api/v1", match: true}, + {url: "/api/v2", match: false}, + }) + benchCase("/api/v1/:param/*", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/entity/", match: true}, + {url: "/api/v1/entity/1", match: true}, + {url: "/api/v", match: false}, + {url: "/api/v2", match: false}, + {url: "/api/v1/", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/true", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/8728382.5", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/#!?", match: false}, + {url: "/api/v1/8728382", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/f0fa66cc-d22e-445b-866d-1d76e776371d", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/123", match: false}, + {url: "/api/v1/12345", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/ent", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/123", match: false}, + {url: "/api/v1/12345", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/ent", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", match: false}, + {url: "/api/v1/en", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/e", match: false}, + {url: "/api/v1/en", match: true}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/12345", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/1", match: false}, + {url: "/api/v1/5", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/1", match: true}, + {url: "/api/v1/5", match: true}, + {url: "/api/v1/15", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/9", match: true}, + {url: "/api/v1/5", match: true}, + {url: "/api/v1/15", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/2005-11-01", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/15", match: false}, + {url: "/api/v1/peach", match: true}, + {url: "/api/v1/p34ch", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/ent", match: false}, + {url: "/api/v1/15", match: false}, + {url: "/api/v1/2022-08-27", match: true}, + {url: "/api/v1/2022/08-27", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/8728382", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/123", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: true}, + {url: "/api/v1/87283827683", match: true}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/true", match: true}, + }) + benchCase("/api/v1/:param", []testparams{ + {url: "/api/v1/entity", match: false}, + {url: "/api/v1/87283827683", match: false}, + {url: "/api/v1/25", match: true}, + {url: "/api/v1/1200", match: true}, + {url: "/api/v1/true", match: false}, + }) + benchCase("/api/v1/:lang/videos/:page", []testparams{ + {url: "/api/v1/try/videos/200", match: false}, + {url: "/api/v1/tr/videos/1800", match: false}, + {url: "/api/v1/tr/videos/100", match: true}, + {url: "/api/v1/e/videos/10", match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: false}, + {url: "/api/v1/tr/1800", match: false}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: true}, + {url: "/api/v1/tr/1800", match: false}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + benchCase("/api/v1/:lang/:page", []testparams{ + {url: "/api/v1/try/200", match: false}, + {url: "/api/v1/tr/1800", match: true}, + {url: "/api/v1/tr/100", match: true}, + {url: "/api/v1/e/10", match: false}, + }) + benchCase("/api/v1/:date/:regex", []testparams{ + {url: "/api/v1/2005-11-01/a", match: false}, + {url: "/api/v1/2005-1101/paach", match: false}, + {url: "/api/v1/2005-11-01/peach", match: true}, + }) +} diff --git a/prefork.go b/prefork.go index 71db86b8d7..f624e24949 100644 --- a/prefork.go +++ b/prefork.go @@ -2,13 +2,16 @@ package fiber import ( "crypto/tls" + "errors" "fmt" + "log" "net" "os" "os/exec" "runtime" "strconv" "strings" + "sync/atomic" "time" "github.com/valyala/fasthttp/reuseport" @@ -17,10 +20,13 @@ import ( const ( envPreforkChildKey = "FIBER_PREFORK_CHILD" envPreforkChildVal = "1" + sleepDuration = 100 * time.Millisecond ) -var testPreforkMaster = false -var testOnPrefork = false +var ( + testPreforkMaster = false + testOnPrefork = false +) // IsChild determines if the current process is a child of Prefork func IsChild() bool { @@ -28,19 +34,21 @@ func IsChild() bool { } // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature -func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) (err error) { +func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) error { + var ln net.Listener + var err error + // 👶 child process 👶 if IsChild() { // use 1 cpu core per child process runtime.GOMAXPROCS(1) - 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(cfg.ListenerNetwork, addr); err != nil { if !cfg.DisableStartupMessage { - time.Sleep(100 * time.Millisecond) // avoid colliding with startup message + time.Sleep(sleepDuration) // avoid colliding with startup message } - return fmt.Errorf("prefork: %v", err) + return fmt.Errorf("prefork: %w", err) } // wrap a tls config around the listener if provided if tlsConfig != nil { @@ -74,7 +82,11 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) (e // kill child procs when master exits defer func() { for _, proc := range childs { - _ = proc.Process.Kill() + if err := proc.Process.Kill(); err != nil { + if !errors.Is(err, os.ErrProcessDone) { + log.Printf("prefork: failed to kill child: %v\n", err) + } + } } }() @@ -83,8 +95,7 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) (e // launch child procs for i := 0; i < max; i++ { - /* #nosec G204 */ - cmd := exec.Command(os.Args[0], os.Args[1:]...) // #nosec G204 + cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec // It's fine to launch the same process again if testPreforkMaster { // When test prefork master, // just start the child process with a dummy cmd, @@ -100,7 +111,7 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) (e ) if err = cmd.Start(); err != nil { - return fmt.Errorf("failed to start a child prefork process, error: %v", err) + return fmt.Errorf("failed to start a child prefork process, error: %w", err) } // store child process @@ -144,27 +155,33 @@ func watchMaster() { // and waits for it to exit p, err := os.FindProcess(os.Getppid()) if err == nil { - _, _ = p.Wait() + _, _ = p.Wait() //nolint:errcheck // It is fine to ignore the error here } - os.Exit(1) + os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork } // if it is equal to 1 (init process ID), // it indicates that the master process has exited - for range time.NewTicker(time.Millisecond * 500).C { + const watchInterval = 500 * time.Millisecond + for range time.NewTicker(watchInterval).C { if os.Getppid() == 1 { - os.Exit(1) + os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork } } } -var dummyChildCmd = "go" +var ( + dummyPid = 1 + dummyChildCmd atomic.Value +) // dummyCmd is for internal prefork testing func dummyCmd() *exec.Cmd { + command := "go" + if storeCommand := dummyChildCmd.Load(); storeCommand != nil && storeCommand != "" { + command = storeCommand.(string) //nolint:forcetypeassert,errcheck // We always store a string in here + } if runtime.GOOS == "windows" { - return exec.Command("cmd", "/C", dummyChildCmd, "version") + return exec.Command("cmd", "/C", command, "version") } - return exec.Command(dummyChildCmd, "version") + return exec.Command(command, "version") } - -var dummyPid = 1 diff --git a/prefork_test.go b/prefork_test.go index a1a680c1bd..1b3441fb80 100644 --- a/prefork_test.go +++ b/prefork_test.go @@ -38,6 +38,7 @@ func Test_App_Prefork_Child_Process(t *testing.T) { if err != nil { require.NoError(t, err) } + //nolint:gosec // We're in a test so using old ciphers is fine config := &tls.Config{Certificates: []tls.Certificate{cer}} go func() { @@ -61,15 +62,16 @@ func Test_App_Prefork_Master_Process(t *testing.T) { require.Nil(t, app.prefork(":3000", nil, listenConfigDefault())) - dummyChildCmd = "invalid" + dummyChildCmd.Store("invalid") err := app.prefork("127.0.0.1:", nil, listenConfigDefault()) require.False(t, err == nil) - dummyChildCmd = "go" + dummyChildCmd.Store("go") } func Test_App_Prefork_Child_Process_Never_Show_Startup_Message(t *testing.T) { + t.Parallel() setupIsChild(t) defer teardownIsChild(t) diff --git a/router.go b/router.go index 7f88c7b99d..04672c7715 100644 --- a/router.go +++ b/router.go @@ -53,14 +53,15 @@ type Route struct { group *Group // Group instance. used for routes in groups // Public fields - Method string `json:"method"` // HTTP method - Name string `json:"name"` // Route's name + Method string `json:"method"` // HTTP method + Name string `json:"name"` // Route's name + //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine Path string `json:"path"` // Original registered route path Params []string `json:"params"` // Case sensitive param keys Handlers []Handler `json:"-"` // Ctx handlers } -func (r *Route) match(detectionPath, path string, params *[maxParams]string) (match bool) { +func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool { // root detectionPath check if r.root && detectionPath == "/" { return true @@ -160,10 +161,9 @@ func (app *App) next(c *DefaultCtx) (match bool, err error) { route := tree[c.indexRoute] // Check if it matches the request path - match = route.match(c.detectionPath, c.path, &c.values) - - // No match, next route + match := route.match(c.detectionPath, c.path, &c.values) if !match { + // No match, next route continue } // Pass route reference and param values @@ -176,19 +176,18 @@ func (app *App) next(c *DefaultCtx) (match bool, err error) { // Execute first handler of route c.indexHandler = 0 - err = route.Handlers[0](c) + 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.matched && app.methodExist(c) { + // If no match, scan stack again if other methods match the request + // Moved from app.handler because middleware may break the route chain err = ErrMethodNotAllowed } - return + return false, err } func (app *App) handler(rctx *fasthttp.RequestCtx) { @@ -222,8 +221,9 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) { } if err != nil { if catch := c.App().ErrorHandler(c, err); catch != nil { - _ = c.SendStatus(StatusInternalServerError) + _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here } + // TODO: Do we need to return here? } } @@ -248,7 +248,7 @@ func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { return route } -func (app *App) copyRoute(route *Route) *Route { +func (*App) copyRoute(route *Route) *Route { return &Route{ // Router booleans use: route.use, @@ -391,6 +391,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { prefixLen-- prefix = prefix[:prefixLen] } + const cacheDuration = 10 * time.Second // Fileserver settings fs := &fasthttp.FS{ Root: root, @@ -399,7 +400,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { AcceptByteRange: false, Compress: false, CompressedFileSuffix: app.config.CompressedFileSuffix, - CacheDuration: 10 * time.Second, + CacheDuration: cacheDuration, IndexNames: []string{"index.html"}, PathRewrite: func(fctx *fasthttp.RequestCtx) []byte { path := fctx.Path() @@ -467,7 +468,6 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { c.Context().SetContentType("") // Issue #420 c.Context().Response.SetStatusCode(StatusOK) c.Context().Response.SetBodyString("") - // Next middleware return c.Next() } diff --git a/router_test.go b/router_test.go index 613bb5d7f3..421d138956 100644 --- a/router_test.go +++ b/router_test.go @@ -2,10 +2,9 @@ // 📃 Github Repository: https://github.com/gofiber/fiber // 📌 API Documentation: https://docs.gofiber.io +//nolint:bodyclose // Much easier to just ignore memory leaks in tests package fiber -// go test -v ./... -run=^$ -bench=Benchmark_Router -benchmem -count=2 - import ( "encoding/json" "errors" @@ -21,7 +20,7 @@ import ( "github.com/valyala/fasthttp" ) -var routesFixture = routeJSON{} +var routesFixture routeJSON func init() { dat, err := os.ReadFile("./.github/testdata/testRoutes.json") @@ -34,6 +33,8 @@ func init() { } func Test_Route_Match_SameLength(t *testing.T) { + t.Parallel() + app := New() app.Get("/:param", func(c Ctx) error { @@ -59,6 +60,8 @@ func Test_Route_Match_SameLength(t *testing.T) { } func Test_Route_Match_Star(t *testing.T) { + t.Parallel() + app := New() app.Get("/*", func(c Ctx) error { @@ -105,6 +108,8 @@ func Test_Route_Match_Star(t *testing.T) { } func Test_Route_Match_Root(t *testing.T) { + t.Parallel() + app := New() app.Get("/", func(c Ctx) error { @@ -121,6 +126,8 @@ func Test_Route_Match_Root(t *testing.T) { } func Test_Route_Match_Parser(t *testing.T) { + t.Parallel() + app := New() app.Get("/foo/:ParamName", func(c Ctx) error { @@ -148,6 +155,8 @@ func Test_Route_Match_Parser(t *testing.T) { } func Test_Route_Match_Middleware(t *testing.T) { + t.Parallel() + app := New() app.Use("/foo/*", func(c Ctx) error { @@ -173,6 +182,8 @@ func Test_Route_Match_Middleware(t *testing.T) { } func Test_Route_Match_UnescapedPath(t *testing.T) { + t.Parallel() + app := New(Config{UnescapePath: true}) app.Use("/créer", func(c Ctx) error { @@ -199,6 +210,8 @@ func Test_Route_Match_UnescapedPath(t *testing.T) { } func Test_Route_Match_WithEscapeChar(t *testing.T) { + t.Parallel() + app := New() // static route and escaped part app.Get("/v1/some/resource/name\\:customVerb", func(c Ctx) error { @@ -243,6 +256,8 @@ func Test_Route_Match_WithEscapeChar(t *testing.T) { } func Test_Route_Match_Middleware_HasPrefix(t *testing.T) { + t.Parallel() + app := New() app.Use("/foo", func(c Ctx) error { @@ -259,6 +274,8 @@ func Test_Route_Match_Middleware_HasPrefix(t *testing.T) { } func Test_Route_Match_Middleware_Root(t *testing.T) { + t.Parallel() + app := New() app.Use("/", func(c Ctx) error { @@ -275,6 +292,8 @@ func Test_Route_Match_Middleware_Root(t *testing.T) { } func Test_Router_Register_Missing_Handler(t *testing.T) { + t.Parallel() + app := New() defer func() { if err := recover(); err != nil { @@ -285,7 +304,9 @@ func Test_Router_Register_Missing_Handler(t *testing.T) { } func Test_Ensure_Router_Interface_Implementation(t *testing.T) { - var app any = (*App)(nil) + t.Parallel() + + var app interface{} = (*App)(nil) _, ok := app.(Router) require.True(t, ok) @@ -295,6 +316,8 @@ func Test_Ensure_Router_Interface_Implementation(t *testing.T) { } func Test_Router_Handler_Catch_Error(t *testing.T) { + t.Parallel() + app := New() app.config.ErrorHandler = func(c Ctx, err error) error { return errors.New("fake error") @@ -312,6 +335,8 @@ func Test_Router_Handler_Catch_Error(t *testing.T) { } func Test_Route_Static_Root(t *testing.T) { + t.Parallel() + dir := "./.github/testdata/fs/css" app := New() app.Static("/", dir, Static{ @@ -347,6 +372,8 @@ func Test_Route_Static_Root(t *testing.T) { } func Test_Route_Static_HasPrefix(t *testing.T) { + t.Parallel() + dir := "./.github/testdata/fs/css" app := New() app.Static("/static", dir, Static{ @@ -537,7 +564,7 @@ func Benchmark_Router_Chain(b *testing.B) { c := &fasthttp.RequestCtx{} - c.Request.Header.SetMethod("GET") + c.Request.Header.SetMethod(MethodGet) c.URI().SetPath("/") b.ResetTimer() for n := 0; n < b.N; n++ { @@ -561,7 +588,7 @@ func Benchmark_Router_WithCompression(b *testing.B) { appHandler := app.Handler() c := &fasthttp.RequestCtx{} - c.Request.Header.SetMethod("GET") + c.Request.Header.SetMethod(MethodGet) c.URI().SetPath("/") b.ResetTimer() for n := 0; n < b.N; n++ { @@ -790,6 +817,6 @@ type testRoute struct { } type routeJSON struct { - TestRoutes []testRoute `json:"testRoutes"` - GithubAPI []testRoute `json:"githubAPI"` + TestRoutes []testRoute `json:"test_routes"` + GithubAPI []testRoute `json:"github_api"` }