diff --git a/.github/README.md b/.github/README.md index 083fec64..45f428f0 100644 --- a/.github/README.md +++ b/.github/README.md @@ -686,13 +686,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_ckb.md b/.github/README_ckb.md index 9b3b2558..92fdba37 100644 --- a/.github/README_ckb.md +++ b/.github/README_ckb.md @@ -685,13 +685,16 @@ For more articles, middlewares, examples or tools check our [awesome list](https **مۆڵەتەکانی دیکە** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_de.md b/.github/README_de.md index c34d90fc..a2d43262 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -655,13 +655,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party MIT licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_es.md b/.github/README_es.md index 21c9c4a9..fd6bd5ae 100644 --- a/.github/README_es.md +++ b/.github/README_es.md @@ -655,14 +655,17 @@ Copyright (c) 2019-presente [Fenny](https://github.com/fenny) y [contribuyentes] **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_fa.md b/.github/README_fa.md index ea553eba..8c8fad73 100644 --- a/.github/README_fa.md +++ b/.github/README_fa.md @@ -807,13 +807,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **مجوزهای کتابخانه شخص ثالث** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_fr.md b/.github/README_fr.md index 093b77b6..11d3db36 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -657,13 +657,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_he.md b/.github/README_he.md index 4caf1405..622bc014 100644 --- a/.github/README_he.md +++ b/.github/README_he.md @@ -832,14 +832,17 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **רישיונות של ספריות צד שלישי** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_id.md b/.github/README_id.md index 80ffd9f6..84066b1c 100644 --- a/.github/README_id.md +++ b/.github/README_id.md @@ -658,14 +658,17 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Lisensi perpustakaan pihak-ketiga** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_it.md b/.github/README_it.md index 50fce76f..233e7b21 100644 --- a/.github/README_it.md +++ b/.github/README_it.md @@ -681,13 +681,16 @@ Copyright (c) 2019-ora [Fenny](https://github.com/fenny) e [Contributors](https: **Licenze di Terze parti ** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_ja.md b/.github/README_ja.md index a5074cbe..007a0aa6 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -660,13 +660,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_ko.md b/.github/README_ko.md index 42116233..d1123704 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -113,7 +113,7 @@ func main() { Go가 설치되어 있는 것을 확인해 주세요 ([download](https://go.dev/dl/)). 버전 1.14 또는 그 이상이어야 합니다. -Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install Fiber with the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: +폴더를 생성하여 당신의 프로젝트를 초기화하고, 폴더 안에서 `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) 를 실행하세요. 그리고 [`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 @@ -661,13 +661,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_nl.md b/.github/README_nl.md index 478a51c8..6451506a 100644 --- a/.github/README_nl.md +++ b/.github/README_nl.md @@ -661,13 +661,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_pt.md b/.github/README_pt.md index 5b6bae23..8ebb91cc 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -657,13 +657,16 @@ O logo oficial foi criado por [Vic Shóstak](https://github.com/koddr) e distrib **Licença das bibliotecas de terceiros** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_ru.md b/.github/README_ru.md index ebbeaa18..b7dfeebb 100644 --- a/.github/README_ru.md +++ b/.github/README_ru.md @@ -664,13 +664,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_sa.md b/.github/README_sa.md index 1532d6ac..e698bd1e 100644 --- a/.github/README_sa.md +++ b/.github/README_sa.md @@ -726,13 +726,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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_tr.md b/.github/README_tr.md index 13096ce8..4bd5136d 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -654,13 +654,16 @@ Telif (c) 2019-günümüz [Fenny](https://github.com/fenny) ve [katkıda bulunan **Üçüncü Parti Kütüphane Lisansları** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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 fd2d2017..4c7fd347 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -79,7 +79,9 @@

- Fiber是一个受到Express启发的Web框架,基于使用Go语言编写的最快的HTTP引擎Fasthttp构建。旨在通过零内存分配高性能服务,使快速开发更加简便。 + Fiber是一个受到 Express 启发的Web框架,基于使用 + Go 语言编写的最快的 HTTP 引擎 + Fasthttp 构建。旨在通过零内存分配高性能服务,使快速开发更加简便。

## ⚡️ 快速入门 @@ -102,7 +104,7 @@ 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) 。

@@ -111,7 +113,7 @@ func main() { ## ⚙️ 安装 -确保已安装`1.14`或更高版本的 Go([下载](https://go.dev/dl/))。 +确保已安装 `1.14` 或更高版本的 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: @@ -126,30 +128,33 @@ go get -u github.com/gofiber/fiber/v3 - 极致[性能](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://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) +- [支持 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/) +- [被翻译成 18 种语言](https://docs.gofiber.io/) - 更多请[探索文档](https://docs.gofiber.io/) ## 💡 哲学 -从[Node.js](https://nodejs.org/en/about/)切换到[Go](https://go.dev/doc/)的新`gopher`在开始构建`Web`应用程序或微服务之前需要经历一段艰难的学习过程。 而`Fiber`,一个遵循**极简主义**和**UNIX 方式**创建的**Web 框架**,使新的`gopher`可以在热烈和可信赖的欢迎中迅速进入`Go`的世界。 +从 [Node.js](https://nodejs.org/en/about/) 切换到 [Go](https://go.dev/doc/) 的新 `gopher` 在开始构建 `Web` +应用程序或微服务之前需要经历一段艰难的学习过程。 而 `Fiber`,一个基于**极简主义**并且遵循 **UNIX 方式**创建的 **Web 框架**, +使新的 `gopher` 可以在热烈和可信赖的欢迎中迅速进入 `Go` 的世界。 `Fiber`受到了互联网上最流行的`Web`框架`Express`的**启发** 。我们结合了`Express`的**易用性**和`Go`的**原始性能** 。如果您曾经使用`Node.js`构建`Web`应用程序(_使用 Express 或类似框架_),那么许多方法和原理对您来说应该**非常易懂**。 -我们会**倾听**用户在[issues](https://github.com/gofiber/fiber/issues)和 Discord [channel](https://gofiber.io/discord)和在互联网上的所有诉求,为了创建一个能让有着任何技术栈的开发者都能在deadline前完成任务的**迅速**,**灵活**以及**友好**的`Go web`框架,就像`Express`在`JavaScript`世界中一样。 +我们会**倾听**用户在 [issues](https://github.com/gofiber/fiber/issues),Discord [channel](https://gofiber.io/discord) +以及在互联网上的所有诉求,为了创建一个能让有着任何技术栈的开发者都能在 deadline 前完成任务的**迅速**,**灵活**以及**友好**的 `Go web` 框架,就像 `Express` 在 `JavaScript` 世界中一样。 ## ⚠️ 限制 * 由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.40.0 已经在 Go 1.16 到 1.19 上测试过。 -* Fiber 与 net/http 接口不兼容。也就是说你无法使用 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。 +* Fiber 与 net/http 接口不兼容。也就是说你无法直接使用例如 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。 ## 👀 示例 -下面列出了一些常见示例。如果您想查看更多代码示例,请访问我们的[Recipes](https://github.com/gofiber/recipes)代码库或[API 文档](https://docs.gofiber.io) 。 +下面列出了一些常见示例。如果您想查看更多代码示例,请访问我们的 [Recipes](https://github.com/gofiber/recipes) 代码库或 [API 文档](https://docs.gofiber.io) 。 #### 📖 [**基础路由**](https://docs.gofiber.io/#basic-routing) @@ -244,7 +249,7 @@ func main() { ``` -#### 📖 [**中间件**](https://docs.gofiber.io/middleware)和[**Next**](https://docs.gofiber.io/api/ctx#next) +#### 📖 [**中间件**](https://docs.gofiber.io/middleware)和 [**Next**](https://docs.gofiber.io/api/ctx#next) ```go func main() { @@ -282,11 +287,12 @@ func main() { 📖 [模版引擎](https://github.com/gofiber/template) 📖 [渲染](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)等等... +如果您要执行部分模版或使用其他引擎,例如[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)包,该包支持多个模版引擎。 +请查看我们的 [Template](https://github.com/gofiber/template) 包,该包支持多个模版引擎。 ```go package main @@ -397,7 +403,7 @@ func main() { } ``` -通过在请求头中设置`Origin`传递任何域来检查 CORS: +通过在请求头中设置 `Origin` 传递任何域来检查 CORS : ```bash curl -H "Origin: http://example.com" --verbose http://localhost:3000 @@ -461,7 +467,7 @@ func main() { } ``` -### 升级到 WebSocket +### 使用 WebSocket 中间件 📖 [Websocket](https://github.com/gofiber/websocket) @@ -565,45 +571,45 @@ func main() { 以下为包含在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) | 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) | 让缓存更加高效并且节省带宽,让web服务不再需要重新响应整个响应体如果响应内容未变更. | -| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | 通过其HTTP服务器运行时间提供JSON格式的暴露变体. | -| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路径,则忽略日志中的图标或从内存中服务. | -| [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) | 恢复中间件从堆栈链中的任何位置的恐慌中恢复,并将控制处理到集中式. [ ErrorHandler](https://docs.gofiber.io/guide/error-handling). | -| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | 为每个请求添加一个requesttid. | -| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session 中间件. 注意: 此中间件使用了我们的存储包. | -| [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. | +| 中间件 | 描述 | +|:---------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------| +| [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) | 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) | 让缓存更加高效并且节省带宽, 让 web 服务在响应内容未变更的情况下不再需要重发送整个响应体 | +| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | 通过其 HTTP 服务器运行时间提供 JSON 格式的暴露变体 | +| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路径,则忽略日志中的图标或从内存中服务 | +| [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) | Session 中间件. 注意: 此中间件使用了我们的存储包. | +| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip 中间件会在判断条为 true 时忽略此次请求 | +| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | 添加请求的最大时间,如果超时则发送给ErrorHandler 进行处理. | ## 🧬 外部中间件 -以下为外部托管的中间件列表,由[Fiber团队](https://github.com/orgs/gofiber/people)维护。 +以下为外部托管的中间件列表,由 [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) | Redirect middleware | -| [rewrite](https://github.com/gofiber/rewrite) | 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支持! | +| 中间件 | 描述 | +|:--------------------------------------------------|:-------------------------------------------------------------------------------------------| +| [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) | 基于 Fasthttp WebSocket for Fiber 实现,支持使用 [Locals](https://docs.gofiber.io/api/ctx#locals) ! | ## 🕶️ Awesome List @@ -611,16 +617,17 @@ For more articles, middlewares, examples or tools check our [awesome list](https ## 👍 贡献 -如果想**感谢**我们或支持`Fiber`的积极发展: +如果想**感谢**我们或支持 `Fiber` 的积极发展: -1. 为`Fiber`[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/)或个人博客上写评论或教程。 +1. 为 [`Fiber`](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)来支持本项目。 ## ☕ 支持者 -`Fiber`是一个开源项目,依靠捐赠来支付账单,例如我们的域名,`gitbook`,`netlify`和无服务器托管。如果要支持`Fiber`,可以 ☕ [**在这里买一杯咖啡**](https://buymeacoff.ee/fenny) +`Fiber`是一个开源项目,依靠捐赠来支付账单,例如我们的域名,`gitbook`,`netlify` 和无服务器托管。如果要支持 `Fiber`, +可以 ☕ [**在这里买一杯咖啡**](https://buymeacoff.ee/fenny) | | User | Donation | | :--------------------------------------------------------- | :----------------------------------------------- | :------- | @@ -647,7 +654,7 @@ For more articles, middlewares, examples or tools check our [awesome list](https Code Contributors -## ⭐️ 星星数增长情况 +## ⭐️ Star 数增长情况 Stargazers over time @@ -657,13 +664,16 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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-TW.md b/.github/README_zh-TW.md index 4037f262..1096fb2f 100644 --- a/.github/README_zh-TW.md +++ b/.github/README_zh-TW.md @@ -657,13 +657,16 @@ Fiber 是一個以贊助維生的開源專案,像是: 網域、gitbook、netli **Third-party library licenses** -- [schema](https://github.com/gorilla/schema/blob/master/LICENSE) -- [isatty](https://github.com/mattn/go-isatty/blob/master/LICENSE) -- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE) - [colorable](https://github.com/mattn/go-colorable/blob/master/LICENSE) -- [fasttemplate](https://github.com/valyala/fasttemplate/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) -- [go-ole](https://github.com/go-ole/go-ole) -- [wmi](https://github.com/StackExchange/wmi) -- [dictpool](https://github.com/savsgio/dictpool) +- [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/workflows/linter.yml b/.github/workflows/linter.yml index 26492c59..4397113a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,4 +1,9 @@ -on: [push, pull_request] +on: + push: + branches: + - master + - main + pull_request: name: Linter jobs: Golint: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 854a1d57..e7d8bade 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,4 +1,9 @@ -on: [push, pull_request] +on: + push: + branches: + - master + - main + pull_request: name: Security jobs: Gosec: @@ -9,4 +14,4 @@ jobs: - name: Run Gosec uses: securego/gosec@master with: - args: -exclude-dir=internal/*/ ./... \ No newline at end of file + args: -exclude-dir=internal/*/ ./... diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml deleted file mode 100644 index f3b872ec..00000000 --- a/.github/workflows/snyk.yml +++ /dev/null @@ -1,11 +0,0 @@ -on: [push, pull_request_target] -name: Snyk security -jobs: - security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Run Snyk to check for vulnerabilities - uses: snyk/actions/golang@master - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml new file mode 100644 index 00000000..bddf020f --- /dev/null +++ b/.github/workflows/vulncheck.yml @@ -0,0 +1,26 @@ +on: + push: + branches: + - master + - main + pull_request: +name: Vulnerability Check +jobs: + Security: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + check-latest: true + - name: Fetch Repository + uses: actions/checkout@v3 + - name: Install Govulncheck + run: | + export GO111MODULE=on + export PATH=${PATH}:`go env GOPATH`/bin + go install golang.org/x/vuln/cmd/govulncheck@latest + - name: Run Govulncheck + run: "`go env GOPATH`/bin/govulncheck ./..." + diff --git a/app.go b/app.go index 1151e8fe..95c52de8 100644 --- a/app.go +++ b/app.go @@ -108,7 +108,6 @@ type App struct { hooks *Hooks // Latest route & group latestRoute *Route - latestGroup *Group // newCtxFunc newCtxFunc func(app *App) CustomCtx // custom binders @@ -117,6 +116,8 @@ type App struct { tlsHandler *TLSHandler // Mount fields mountFields *mountFields + // Indicates if the value was explicitly configured + configured Config } // Config is a struct holding the server settings. @@ -365,6 +366,11 @@ type Config struct { // // Default: nil StructValidator StructValidator + + // RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. + // + // Optional. Default: DefaultMethods + RequestMethods []string } // Static defines configuration options when defining static assets. @@ -430,6 +436,19 @@ const ( DefaultCompressedFileSuffix = ".fiber.gz" ) +// HTTP methods enabled by default +var DefaultMethods = []string{ + MethodGet, + MethodHead, + MethodPost, + MethodPut, + MethodDelete, + MethodConnect, + MethodOptions, + MethodTrace, + MethodPatch, +} + // DefaultErrorHandler that process return errors from handlers var DefaultErrorHandler = func(c Ctx, err error) error { code := StatusInternalServerError @@ -454,15 +473,11 @@ var DefaultErrorHandler = func(c Ctx, err error) error { func New(config ...Config) *App { // Create a new app app := &App{ - // Create router stack - stack: make([][]*Route, len(intMethod)), - treeStack: make([]map[string][]*Route, len(intMethod)), // Create config config: Config{}, getBytes: utils.UnsafeBytes, getString: utils.UnsafeString, latestRoute: &Route{}, - latestGroup: &Group{}, customBinders: []CustomBinder{}, } @@ -484,6 +499,9 @@ func New(config ...Config) *App { app.config = config[0] } + // Initialize configured before defaults are set + app.configured = app.config + // Override default values if app.config.BodyLimit == 0 { app.config.BodyLimit = DefaultBodyLimit @@ -517,12 +535,19 @@ func New(config ...Config) *App { if app.config.XMLEncoder == nil { app.config.XMLEncoder = xml.Marshal } + if len(app.config.RequestMethods) == 0 { + app.config.RequestMethods = DefaultMethods + } app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies)) for _, ipAddress := range app.config.TrustedProxies { app.handleTrustedProxy(ipAddress) } + // Create router stack + app.stack = make([][]*Route, len(app.config.RequestMethods)) + app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods)) + // Override colors app.config.ColorScheme = defaultColors(app.config.ColorScheme) @@ -571,8 +596,10 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { // Name Assign name to specific route. func (app *App) Name(name string) Router { app.mutex.Lock() - if strings.HasPrefix(app.latestRoute.path, app.latestGroup.Prefix) { - app.latestRoute.Name = app.latestGroup.name + name + + latestGroup := app.latestRoute.group + if latestGroup != nil { + app.latestRoute.Name = latestGroup.name + name } else { app.latestRoute.Name = name } @@ -640,6 +667,7 @@ func (app *App) GetRoutes(filterUseOption ...bool) []Route { func (app *App) Use(args ...any) Router { var prefix string var subApp *App + var prefixes []string var handlers []Handler for i := 0; i < len(args); i++ { @@ -648,6 +676,8 @@ func (app *App) Use(args ...any) Router { prefix = arg case *App: subApp = arg + case []string: + prefixes = arg case Handler: handlers = append(handlers, arg) default: @@ -655,13 +685,19 @@ func (app *App) Use(args ...any) Router { } } - if subApp != nil { - app.mount(prefix, subApp) - return app + if len(prefixes) == 0 { + prefixes = append(prefixes, prefix) + } + + for _, prefix := range prefixes { + if subApp != nil { + app.mount(prefix, subApp) + return app + } + + app.register([]string{methodUse}, prefix, nil, nil, handlers...) } - app.register([]string{methodUse}, prefix, nil, handlers...) - return app } @@ -720,7 +756,7 @@ func (app *App) Patch(path string, handler Handler, middleware ...Handler) Route // Add allows you to specify multiple HTTP methods to register a route. func (app *App) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { - return app.register(methods, path, handler, middleware...) + return app.register(methods, path, nil, handler, middleware...) } // Static will create a file server serving static files @@ -730,8 +766,7 @@ func (app *App) Static(prefix, root string, config ...Static) Router { // All will register the handler on all HTTP methods func (app *App) All(path string, handler Handler, middleware ...Handler) Router { - app.Add(intMethod, path, handler, middleware...) - return app + return app.Add(app.config.RequestMethods, path, handler, middleware...) } // Group is used for Routes with common prefix to define a new sub-router with optional middleware. @@ -739,10 +774,10 @@ func (app *App) All(path string, handler Handler, middleware ...Handler) Router // api := app.Group("/api") // api.Get("/users", handler) func (app *App) Group(prefix string, handlers ...Handler) Router { - if len(handlers) > 0 { - app.register([]string{methodUse}, prefix, nil, handlers...) - } grp := &Group{Prefix: prefix, app: app} + if len(handlers) > 0 { + app.register([]string{methodUse}, prefix, grp, nil, handlers...) + } if err := app.hooks.executeOnGroupHooks(*grp); err != nil { panic(err) } @@ -961,7 +996,10 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { if prefix != "" && strings.HasPrefix(ctx.Path(), prefix) { parts := len(strings.Split(prefix, "/")) if mountedPrefixParts <= parts { - mountedErrHandler = subApp.config.ErrorHandler + if subApp.configured.ErrorHandler != nil { + mountedErrHandler = subApp.config.ErrorHandler + } + mountedPrefixParts = parts } } diff --git a/app_test.go b/app_test.go index a3be17f0..9653028f 100644 --- a/app_test.go +++ b/app_test.go @@ -398,6 +398,52 @@ func Test_App_Not_Use_StrictRouting(t *testing.T) { require.Equal(t, StatusOK, resp.StatusCode, "Status code") } +func Test_App_Use_MultiplePrefix(t *testing.T) { + app := New() + + app.Use([]string{"/john", "/doe"}, func(c Ctx) error { + return c.SendString(c.Path()) + }) + + g := app.Group("/test") + g.Use([]string{"/john", "/doe"}, func(c Ctx) error { + return c.SendString(c.Path()) + }) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/john", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "/john", string(body)) + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "/doe", string(body)) + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/john", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "/test/john", string(body)) + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/doe", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + 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) { app := New(Config{StrictRouting: true}) @@ -432,13 +478,32 @@ func Test_App_Use_StrictRouting(t *testing.T) { } func Test_App_Add_Method_Test(t *testing.T) { - app := New() defer func() { if err := recover(); err != nil { - require.Equal(t, "add: invalid http method JOHN\n", fmt.Sprintf("%v", err)) + require.Equal(t, "add: invalid http method JANE\n", fmt.Sprintf("%v", err)) } }() + + methods := append(DefaultMethods, "JOHN") + app := New(Config{ + RequestMethods: methods, + }) + app.Add([]string{"JOHN"}, "/doe", testEmptyHandler) + + resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusOK, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code") + + resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/doe", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, StatusBadRequest, resp.StatusCode, "Status code") + + app.Add([]string{"JANE"}, "/doe", testEmptyHandler) } // go test -run Test_App_GETOnly @@ -484,7 +549,7 @@ func Test_App_Chaining(t *testing.T) { return c.SendStatus(202) }) // check handler count for registered HEAD route - require.Equal(t, 5, len(app.stack[methodInt(MethodHead)][0].Handlers), "app.Test(req)") + require.Equal(t, 5, len(app.stack[app.methodInt(MethodHead)][0].Handlers), "app.Test(req)") req := httptest.NewRequest(MethodPost, "/john", nil) @@ -581,15 +646,16 @@ func Test_App_Route_Naming(t *testing.T) { app.Name("doe") jane := app.Group("/jane").Name("jane.") + group := app.Group("/group") + subGroup := jane.Group("/sub-group").Name("sub.") + jane.Get("/test", handler).Name("test") jane.Trace("/trace", handler).Name("trace") - group := app.Group("/group") group.Get("/test", handler).Name("test") app.Post("/post", handler).Name("post") - subGroup := jane.Group("/sub-group").Name("sub.") subGroup.Get("/done", handler).Name("done") require.Equal(t, "post", app.GetRoute("post").Name) @@ -1218,16 +1284,17 @@ func Test_App_Stack(t *testing.T) { app.Post("/path3", testEmptyHandler) stack := app.Stack() - require.Equal(t, 9, len(stack)) - require.Equal(t, 3, len(stack[methodInt(MethodGet)])) - require.Equal(t, 1, len(stack[methodInt(MethodHead)])) - require.Equal(t, 2, len(stack[methodInt(MethodPost)])) - require.Equal(t, 1, len(stack[methodInt(MethodPut)])) - require.Equal(t, 1, len(stack[methodInt(MethodPatch)])) - require.Equal(t, 1, len(stack[methodInt(MethodDelete)])) - require.Equal(t, 1, len(stack[methodInt(MethodConnect)])) - require.Equal(t, 1, len(stack[methodInt(MethodOptions)])) - require.Equal(t, 1, len(stack[methodInt(MethodTrace)])) + methodList := app.config.RequestMethods + require.Equal(t, len(methodList), len(stack)) + require.Equal(t, 3, len(stack[app.methodInt(MethodGet)])) + require.Equal(t, 3, len(stack[app.methodInt(MethodHead)])) + require.Equal(t, 2, len(stack[app.methodInt(MethodPost)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodPut)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodPatch)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodDelete)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodConnect)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodOptions)])) + require.Equal(t, 1, len(stack[app.methodInt(MethodTrace)])) } // go test -run Test_App_HandlersCount @@ -1477,6 +1544,19 @@ func Test_App_SetTLSHandler(t *testing.T) { require.Equal(t, "example.golang", c.ClientHelloInfo().ServerName) } +func Test_App_AddCustomRequestMethod(t *testing.T) { + methods := append(DefaultMethods, "TEST") + app := New(Config{ + RequestMethods: methods, + }) + appMethods := app.config.RequestMethods + + // method name is always uppercase - https://datatracker.ietf.org/doc/html/rfc7231#section-4.1 + require.Equal(t, len(app.stack), len(appMethods)) + require.Equal(t, len(app.stack), len(appMethods)) + require.Equal(t, "TEST", appMethods[len(appMethods)-1]) +} + func TestApp_GetRoutes(t *testing.T) { app := New() app.Use(func(c Ctx) error { @@ -1488,7 +1568,7 @@ func TestApp_GetRoutes(t *testing.T) { app.Delete("/delete", handler).Name("delete") app.Post("/post", handler).Name("post") routes := app.GetRoutes(false) - require.Equal(t, 11, len(routes)) + require.Equal(t, 2+len(app.config.RequestMethods), len(routes)) methodMap := map[string]string{"/delete": "delete", "/post": "post"} for _, route := range routes { name, ok := methodMap[route.Path] @@ -1504,5 +1584,4 @@ func TestApp_GetRoutes(t *testing.T) { require.Equal(t, true, ok) require.Equal(t, name, route.Name) } - } diff --git a/ctx.go b/ctx.go index 159947e5..e56f47df 100644 --- a/ctx.go +++ b/ctx.go @@ -732,7 +732,7 @@ func (c *DefaultCtx) Location(path string) { func (c *DefaultCtx) Method(override ...string) string { if len(override) > 0 { method := utils.ToUpper(override[0]) - mINT := methodInt(method) + mINT := c.app.methodInt(method) if mINT == -1 { return c.method } @@ -855,29 +855,26 @@ func (c *DefaultCtx) Path(override ...string) string { return c.path } -// Scheme contains the request scheme string: http or https for TLS requests. -// Use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Scheme contains the request protocol string: http or https for TLS requests. +// 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" } - scheme := "http" if !c.IsProxyTrusted() { - return scheme + return "http" } + + scheme := "http" c.fasthttp.Request.Header.VisitAll(func(key, val []byte) { if len(key) < 12 { - return // X-Forwarded- - } else if bytes.HasPrefix(key, []byte("X-Forwarded-")) { - v := c.app.getString(val) - if bytes.Equal(key, []byte(HeaderXForwardedProto)) { - commaPos := strings.Index(v, ",") - if commaPos != -1 { - scheme = v[:commaPos] - } else { - scheme = v - } - } else if bytes.Equal(key, []byte(HeaderXForwardedProtocol)) { + return // Neither "X-Forwarded-" nor "X-Url-Scheme" + } + switch { + case bytes.HasPrefix(key, []byte("X-Forwarded-")): + if bytes.Equal(key, []byte(HeaderXForwardedProto)) || + bytes.Equal(key, []byte(HeaderXForwardedProtocol)) { + v := c.app.getString(val) commaPos := strings.Index(v, ",") if commaPos != -1 { scheme = v[:commaPos] @@ -887,7 +884,8 @@ func (c *DefaultCtx) Scheme() string { } else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) { scheme = "https" } - } else if bytes.Equal(key, []byte(HeaderXUrlScheme)) { + + case bytes.Equal(key, []byte(HeaderXUrlScheme)): scheme = c.app.getString(val) } }) @@ -1135,9 +1133,9 @@ func (c *DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path st return storage.Set(path, content, 0) } -// Secure returns a boolean property, that is true, if a TLS connection is established. +// Secure returns whether a secure connection was established. func (c *DefaultCtx) Secure() bool { - return c.fasthttp.IsTLS() + return c.Protocol() == "https" } // Send sets the HTTP response body without copying it. diff --git a/ctx_interface.go b/ctx_interface.go index 5f363baf..de3d16cb 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -255,7 +255,7 @@ type Ctx interface { // SaveFileToStorage saves any multipart file to an external storage system. SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error - // Secure returns a boolean property, that is true, if a TLS connection is established. + // Secure returns whether a secure connection was established. Secure() bool // Send sets the HTTP response body without copying it. @@ -427,7 +427,7 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) { // Set method c.method = c.app.getString(fctx.Request.Header.Method()) - c.methodINT = methodInt(c.method) + c.methodINT = c.app.methodInt(c.method) // Prettify path c.configDependentPaths() @@ -456,7 +456,7 @@ func (c *DefaultCtx) setReq(fctx *fasthttp.RequestCtx) { // Set method c.method = c.app.getString(fctx.Request.Header.Method()) - c.methodINT = methodInt(c.method) + c.methodINT = c.app.methodInt(c.method) // Prettify path c.configDependentPaths() diff --git a/go.mod b/go.mod index 265ceccf..fec57548 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gofiber/fiber/v3 go 1.19 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 @@ -10,8 +11,7 @@ require ( 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.41.0 - github.com/valyala/fasttemplate v1.2.1 + github.com/valyala/fasthttp v1.42.0 ) require ( @@ -21,6 +21,6 @@ require ( 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.1.0 // indirect + golang.org/x/sys v0.2.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 63c3664a..d1f071c0 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4= +github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk= github.com/gofiber/utils/v2 v2.0.0-beta.1 h1:ACfPdqeclx+BFIja19UjkKx7k3r5tmpILpNgzrfPLKs= github.com/gofiber/utils/v2 v2.0.0-beta.1/go.mod h1:CG89nDoIkEFIJaw5LdLO9AmBM11odse/LC79KQujm74= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -31,8 +33,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw 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/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasthttp v1.42.0 h1:LBMyqvJR8DEBgN79oI8dGbkuj5Lm9jbHESxH131TTN8= +github.com/valyala/fasthttp v1.42.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,6 +60,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc 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/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 e7becbeb..1470e970 100644 --- a/group.go +++ b/group.go @@ -7,13 +7,13 @@ package fiber import ( "fmt" "reflect" - "strings" ) // Group struct type Group struct { - app *App - name string + app *App + parentGroup *Group + name string Prefix string } @@ -21,15 +21,14 @@ type Group struct { // Name Assign name to specific route. func (grp *Group) Name(name string) Router { grp.app.mutex.Lock() - if strings.HasPrefix(grp.Prefix, grp.app.latestGroup.Prefix) { - grp.name = grp.app.latestGroup.name + name + + if grp.parentGroup != nil { + grp.name = grp.parentGroup.name + name } else { grp.name = name } - grp.app.latestGroup = grp - - if err := grp.app.hooks.executeOnGroupNameHooks(*grp.app.latestGroup); err != nil { + if err := grp.app.hooks.executeOnGroupNameHooks(*grp); err != nil { panic(err) } grp.app.mutex.Unlock() @@ -59,8 +58,9 @@ func (grp *Group) Name(name string) Router { // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... func (grp *Group) Use(args ...any) Router { - prefix := "" var subApp *App + var prefix string + var prefixes []string var handlers []Handler for i := 0; i < len(args); i++ { @@ -69,6 +69,8 @@ func (grp *Group) Use(args ...any) Router { prefix = arg case *App: subApp = arg + case []string: + prefixes = arg case Handler: handlers = append(handlers, arg) default: @@ -76,12 +78,18 @@ func (grp *Group) Use(args ...any) Router { } } - if subApp != nil { - grp.mount(prefix, subApp) - return grp + if len(prefixes) == 0 { + prefixes = append(prefixes, prefix) } - grp.app.register([]string{methodUse}, getGroupPath(grp.Prefix, prefix), nil, handlers...) + for _, prefix := range prefixes { + if subApp != nil { + grp.mount(prefix, subApp) + return grp + } + + grp.app.register([]string{methodUse}, getGroupPath(grp.Prefix, prefix), grp, nil, handlers...) + } return grp } @@ -89,8 +97,7 @@ func (grp *Group) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. func (grp *Group) Get(path string, handler Handler, middleware ...Handler) Router { - path = getGroupPath(grp.Prefix, path) - return grp.app.Add([]string{MethodGet}, path, handler, middleware...) + return grp.Add([]string{MethodGet}, path, handler, middleware...) } // Head registers a route for HEAD methods that asks for a response identical @@ -142,7 +149,7 @@ func (grp *Group) Patch(path string, handler Handler, middleware ...Handler) Rou // Add allows you to specify multiple HTTP methods to register a route. func (grp *Group) Add(methods []string, path string, handler Handler, middleware ...Handler) Router { - return grp.app.register(methods, getGroupPath(grp.Prefix, path), handler, middleware...) + return grp.app.register(methods, getGroupPath(grp.Prefix, path), grp, handler, middleware...) } // Static will create a file server serving static files @@ -152,7 +159,7 @@ func (grp *Group) Static(prefix, root string, config ...Static) Router { // All will register the handler on all HTTP methods func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router { - grp.Add(intMethod, path, handler, middleware...) + _ = grp.Add(grp.app.config.RequestMethods, path, handler, middleware...) return grp } @@ -163,9 +170,17 @@ func (grp *Group) All(path string, handler Handler, middleware ...Handler) Route func (grp *Group) Group(prefix string, handlers ...Handler) Router { prefix = getGroupPath(grp.Prefix, prefix) if len(handlers) > 0 { - grp.app.register([]string{methodUse}, prefix, nil, handlers...) + _ = grp.app.register([]string{methodUse}, prefix, grp, nil, handlers...) } - return grp.app.Group(prefix) + + // Create new group + newGrp := &Group{Prefix: prefix, app: grp.app, parentGroup: grp} + if err := grp.app.hooks.executeOnGroupHooks(*newGrp); err != nil { + panic(err) + } + + 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 db7efc54..a2588aa4 100644 --- a/helpers.go +++ b/helpers.go @@ -75,8 +75,9 @@ func (app *App) quoteString(raw string) string { } // Scan stack if other methods match the request -func methodExist(c *DefaultCtx) (exist bool) { - for i := 0; i < len(intMethod); i++ { +func (app *App) methodExist(c *DefaultCtx) (exist bool) { + methods := app.config.RequestMethods + for i := 0; i < len(methods); i++ { // Skip original method if c.getMethodINT() == i { continue @@ -107,7 +108,7 @@ func methodExist(c *DefaultCtx) (exist bool) { // We matched exist = true // Add method to Allow header - c.Append(HeaderAllow, intMethod[i]) + c.Append(HeaderAllow, methods[i]) // Break stack loop break } @@ -117,8 +118,9 @@ func methodExist(c *DefaultCtx) (exist bool) { } // Scan stack if other methods match the request -func methodExistCustom(c CustomCtx) (exist bool) { - for i := 0; i < len(intMethod); i++ { +func (app *App) methodExistCustom(c CustomCtx) (exist bool) { + methods := app.config.RequestMethods + for i := 0; i < len(methods); i++ { // Skip original method if c.getMethodINT() == i { continue @@ -149,7 +151,7 @@ func methodExistCustom(c CustomCtx) (exist bool) { // We matched exist = true // Add method to Allow header - c.Append(HeaderAllow, intMethod[i]) + c.Append(HeaderAllow, methods[i]) // Break stack loop break } @@ -323,42 +325,41 @@ var getBytesImmutable = func(s string) (b []byte) { } // HTTP methods and their unique INTs -func methodInt(s string) int { - switch s { - case MethodGet: - return 0 - case MethodHead: - return 1 - case MethodPost: - return 2 - case MethodPut: - return 3 - case MethodDelete: - return 4 - case MethodConnect: - return 5 - case MethodOptions: - return 6 - case MethodTrace: - return 7 - case MethodPatch: - return 8 - default: - return -1 +func (app *App) methodInt(s string) int { + // For better performance + if len(app.configured.RequestMethods) == 0 { + switch s { + case MethodGet: + return 0 + case MethodHead: + return 1 + case MethodPost: + return 2 + case MethodPut: + return 3 + case MethodDelete: + return 4 + case MethodConnect: + return 5 + case MethodOptions: + return 6 + case MethodTrace: + return 7 + case MethodPatch: + return 8 + default: + return -1 + } } -} -// HTTP methods slice -var intMethod = []string{ - MethodGet, - MethodHead, - MethodPost, - MethodPut, - MethodDelete, - MethodConnect, - MethodOptions, - MethodTrace, - MethodPatch, + // For method customization + for i, v := range app.config.RequestMethods { + if s == v { + return i + } + } + + return -1 } // HTTP methods were copied from net/http. @@ -399,65 +400,73 @@ const ( MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" ) -// HTTP status codes were copied from net/http. +// HTTP status codes were copied from https://github.com/nginx/nginx/blob/67d2a9541826ecd5db97d604f23460210fd3e517/conf/mime.types with the following updates: +// - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation +// - Add StatusSwitchProxy (306) +// NOTE: Keep this list in sync with statusMessage const ( - StatusContinue = 100 // RFC 7231, 6.2.1 - StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2 - StatusProcessing = 102 // RFC 2518, 10.1 - StatusEarlyHints = 103 // RFC 8297 - StatusOK = 200 // RFC 7231, 6.3.1 - StatusCreated = 201 // RFC 7231, 6.3.2 - StatusAccepted = 202 // RFC 7231, 6.3.3 - StatusNonAuthoritativeInformation = 203 // RFC 7231, 6.3.4 - StatusNoContent = 204 // RFC 7231, 6.3.5 - StatusResetContent = 205 // RFC 7231, 6.3.6 - StatusPartialContent = 206 // RFC 7233, 4.1 - StatusMultiStatus = 207 // RFC 4918, 11.1 - StatusAlreadyReported = 208 // RFC 5842, 7.1 - StatusIMUsed = 226 // RFC 3229, 10.4.1 - StatusMultipleChoices = 300 // RFC 7231, 6.4.1 - StatusMovedPermanently = 301 // RFC 7231, 6.4.2 - StatusFound = 302 // RFC 7231, 6.4.3 - StatusSeeOther = 303 // RFC 7231, 6.4.4 - StatusNotModified = 304 // RFC 7232, 4.1 - StatusUseProxy = 305 // RFC 7231, 6.4.5 - StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 - StatusPermanentRedirect = 308 // RFC 7538, 3 - StatusBadRequest = 400 // RFC 7231, 6.5.1 - StatusUnauthorized = 401 // RFC 7235, 3.1 - StatusPaymentRequired = 402 // RFC 7231, 6.5.2 - StatusForbidden = 403 // RFC 7231, 6.5.3 - StatusNotFound = 404 // RFC 7231, 6.5.4 - StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5 - StatusNotAcceptable = 406 // RFC 7231, 6.5.6 - StatusProxyAuthRequired = 407 // RFC 7235, 3.2 - StatusRequestTimeout = 408 // RFC 7231, 6.5.7 - StatusConflict = 409 // RFC 7231, 6.5.8 - StatusGone = 410 // RFC 7231, 6.5.9 - StatusLengthRequired = 411 // RFC 7231, 6.5.10 - StatusPreconditionFailed = 412 // RFC 7232, 4.2 - StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11 - StatusRequestURITooLong = 414 // RFC 7231, 6.5.12 - StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13 - StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 - StatusExpectationFailed = 417 // RFC 7231, 6.5.14 - StatusTeapot = 418 // RFC 7168, 2.3.3 - StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 - StatusUnprocessableEntity = 422 // RFC 4918, 11.2 - StatusLocked = 423 // RFC 4918, 11.3 - StatusFailedDependency = 424 // RFC 4918, 11.4 - StatusTooEarly = 425 // RFC 8470, 5.2. - StatusUpgradeRequired = 426 // RFC 7231, 6.5.15 - StatusPreconditionRequired = 428 // RFC 6585, 3 - StatusTooManyRequests = 429 // RFC 6585, 4 - StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 - StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 - StatusInternalServerError = 500 // RFC 7231, 6.6.1 - StatusNotImplemented = 501 // RFC 7231, 6.6.2 - StatusBadGateway = 502 // RFC 7231, 6.6.3 - StatusServiceUnavailable = 503 // RFC 7231, 6.6.4 - StatusGatewayTimeout = 504 // RFC 7231, 6.6.5 - StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6 + StatusContinue = 100 // RFC 9110, 15.2.1 + StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 + StatusProcessing = 102 // RFC 2518, 10.1 + StatusEarlyHints = 103 // RFC 8297 + + StatusOK = 200 // RFC 9110, 15.3.1 + StatusCreated = 201 // RFC 9110, 15.3.2 + StatusAccepted = 202 // RFC 9110, 15.3.3 + StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4 + StatusNoContent = 204 // RFC 9110, 15.3.5 + StatusResetContent = 205 // RFC 9110, 15.3.6 + StatusPartialContent = 206 // RFC 9110, 15.3.7 + StatusMultiStatus = 207 // RFC 4918, 11.1 + StatusAlreadyReported = 208 // RFC 5842, 7.1 + StatusIMUsed = 226 // RFC 3229, 10.4.1 + + StatusMultipleChoices = 300 // RFC 9110, 15.4.1 + StatusMovedPermanently = 301 // RFC 9110, 15.4.2 + StatusFound = 302 // RFC 9110, 15.4.3 + StatusSeeOther = 303 // RFC 9110, 15.4.4 + StatusNotModified = 304 // RFC 9110, 15.4.5 + StatusUseProxy = 305 // RFC 9110, 15.4.6 + StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) + StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 + StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 + + StatusBadRequest = 400 // RFC 9110, 15.5.1 + StatusUnauthorized = 401 // RFC 9110, 15.5.2 + StatusPaymentRequired = 402 // RFC 9110, 15.5.3 + StatusForbidden = 403 // RFC 9110, 15.5.4 + StatusNotFound = 404 // RFC 9110, 15.5.5 + StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 + StatusNotAcceptable = 406 // RFC 9110, 15.5.7 + StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 + StatusRequestTimeout = 408 // RFC 9110, 15.5.9 + StatusConflict = 409 // RFC 9110, 15.5.10 + StatusGone = 410 // RFC 9110, 15.5.11 + StatusLengthRequired = 411 // RFC 9110, 15.5.12 + StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 + StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 + StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 + StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 + StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 + StatusExpectationFailed = 417 // RFC 9110, 15.5.18 + StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) + StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 + StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 + StatusLocked = 423 // RFC 4918, 11.3 + StatusFailedDependency = 424 // RFC 4918, 11.4 + StatusTooEarly = 425 // RFC 8470, 5.2. + StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 + StatusPreconditionRequired = 428 // RFC 6585, 3 + StatusTooManyRequests = 429 // RFC 6585, 4 + StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 + StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 + + StatusInternalServerError = 500 // RFC 9110, 15.6.1 + StatusNotImplemented = 501 // RFC 9110, 15.6.2 + StatusBadGateway = 502 // RFC 9110, 15.6.3 + StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 + StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 + StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 StatusInsufficientStorage = 507 // RFC 4918, 11.5 StatusLoopDetected = 508 // RFC 5842, 7.2 @@ -467,46 +476,47 @@ const ( // Errors var ( - ErrBadRequest = NewError(StatusBadRequest) // RFC 7231, 6.5.1 - ErrUnauthorized = NewError(StatusUnauthorized) // RFC 7235, 3.1 - ErrPaymentRequired = NewError(StatusPaymentRequired) // RFC 7231, 6.5.2 - ErrForbidden = NewError(StatusForbidden) // RFC 7231, 6.5.3 - ErrNotFound = NewError(StatusNotFound) // RFC 7231, 6.5.4 - ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // RFC 7231, 6.5.5 - ErrNotAcceptable = NewError(StatusNotAcceptable) // RFC 7231, 6.5.6 - ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // RFC 7235, 3.2 - ErrRequestTimeout = NewError(StatusRequestTimeout) // RFC 7231, 6.5.7 - ErrConflict = NewError(StatusConflict) // RFC 7231, 6.5.8 - ErrGone = NewError(StatusGone) // RFC 7231, 6.5.9 - ErrLengthRequired = NewError(StatusLengthRequired) // RFC 7231, 6.5.10 - ErrPreconditionFailed = NewError(StatusPreconditionFailed) // RFC 7232, 4.2 - ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // RFC 7231, 6.5.11 - ErrRequestURITooLong = NewError(StatusRequestURITooLong) // RFC 7231, 6.5.12 - ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // RFC 7231, 6.5.13 - ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // RFC 7233, 4.4 - ErrExpectationFailed = NewError(StatusExpectationFailed) // RFC 7231, 6.5.14 - ErrTeapot = NewError(StatusTeapot) // RFC 7168, 2.3.3 - ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // RFC 7540, 9.1.2 - ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // RFC 4918, 11.2 - ErrLocked = NewError(StatusLocked) // RFC 4918, 11.3 - ErrFailedDependency = NewError(StatusFailedDependency) // RFC 4918, 11.4 - ErrTooEarly = NewError(StatusTooEarly) // RFC 8470, 5.2. - ErrUpgradeRequired = NewError(StatusUpgradeRequired) // RFC 7231, 6.5.15 - ErrPreconditionRequired = NewError(StatusPreconditionRequired) // RFC 6585, 3 - ErrTooManyRequests = NewError(StatusTooManyRequests) // RFC 6585, 4 - ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // RFC 6585, 5 - ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // RFC 7725, 3 - ErrInternalServerError = NewError(StatusInternalServerError) // RFC 7231, 6.6.1 - ErrNotImplemented = NewError(StatusNotImplemented) // RFC 7231, 6.6.2 - ErrBadGateway = NewError(StatusBadGateway) // RFC 7231, 6.6.3 - ErrServiceUnavailable = NewError(StatusServiceUnavailable) // RFC 7231, 6.6.4 - ErrGatewayTimeout = NewError(StatusGatewayTimeout) // RFC 7231, 6.6.5 - ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // RFC 7231, 6.6.6 - ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // RFC 2295, 8.1 - ErrInsufficientStorage = NewError(StatusInsufficientStorage) // RFC 4918, 11.5 - ErrLoopDetected = NewError(StatusLoopDetected) // RFC 5842, 7.2 - ErrNotExtended = NewError(StatusNotExtended) // RFC 2774, 7 - ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // RFC 6585, 6 + ErrBadRequest = NewError(StatusBadRequest) // 400 + ErrUnauthorized = NewError(StatusUnauthorized) // 401 + ErrPaymentRequired = NewError(StatusPaymentRequired) // 402 + ErrForbidden = NewError(StatusForbidden) // 403 + ErrNotFound = NewError(StatusNotFound) // 404 + ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405 + ErrNotAcceptable = NewError(StatusNotAcceptable) // 406 + ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407 + ErrRequestTimeout = NewError(StatusRequestTimeout) // 408 + ErrConflict = NewError(StatusConflict) // 409 + ErrGone = NewError(StatusGone) // 410 + ErrLengthRequired = NewError(StatusLengthRequired) // 411 + ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412 + ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413 + ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414 + ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415 + ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416 + ErrExpectationFailed = NewError(StatusExpectationFailed) // 417 + ErrTeapot = NewError(StatusTeapot) // 418 + ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421 + ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422 + ErrLocked = NewError(StatusLocked) // 423 + ErrFailedDependency = NewError(StatusFailedDependency) // 424 + ErrTooEarly = NewError(StatusTooEarly) // 425 + ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426 + ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428 + ErrTooManyRequests = NewError(StatusTooManyRequests) // 429 + ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431 + ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451 + + ErrInternalServerError = NewError(StatusInternalServerError) // 500 + ErrNotImplemented = NewError(StatusNotImplemented) // 501 + ErrBadGateway = NewError(StatusBadGateway) // 502 + ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503 + ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504 + ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505 + ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506 + ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507 + ErrLoopDetected = NewError(StatusLoopDetected) // 508 + ErrNotExtended = NewError(StatusNotExtended) // 510 + ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511 ) // HTTP Headers were copied from net/http. diff --git a/internal/memory/memory.go b/internal/memory/memory.go index 33273e28..417ae89a 100644 --- a/internal/memory/memory.go +++ b/internal/memory/memory.go @@ -47,8 +47,9 @@ func (s *Storage) Set(key string, val interface{}, ttl time.Duration) { if ttl > 0 { exp = uint32(ttl.Seconds()) + atomic.LoadUint32(&utils.Timestamp) } + i := item{exp, val} s.Lock() - s.data[key] = item{exp, val} + s.data[key] = i s.Unlock() } @@ -61,8 +62,9 @@ func (s *Storage) Delete(key string) { // Reset all keys func (s *Storage) Reset() { + nd := make(map[string]item) s.Lock() - s.data = make(map[string]item) + s.data = nd s.Unlock() } @@ -74,17 +76,23 @@ func (s *Storage) gc(sleep time.Duration) { 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 <= atomic.LoadUint32(&utils.Timestamp) { + 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 { - delete(s.data, expired[i]) + v := s.data[expired[i]] + if v.e != 0 && v.e <= ts { + delete(s.data, expired[i]) + } } s.Unlock() } diff --git a/internal/storage/memory/memory.go b/internal/storage/memory/memory.go index e18cac27..9efd521f 100644 --- a/internal/storage/memory/memory.go +++ b/internal/storage/memory/memory.go @@ -70,8 +70,9 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error { expire = uint32(exp.Seconds()) + atomic.LoadUint32(&utils.Timestamp) } + e := entry{val, expire} s.mux.Lock() - s.db[key] = entry{val, expire} + s.db[key] = e s.mux.Unlock() return nil } @@ -90,8 +91,9 @@ func (s *Storage) Delete(key string) error { // Reset all keys func (s *Storage) Reset() error { + ndb := make(map[string]entry) s.mux.Lock() - s.db = make(map[string]entry) + s.db = ndb s.mux.Unlock() return nil } @@ -112,17 +114,23 @@ func (s *Storage) gc() { case <-s.done: return case <-ticker.C: + ts := atomic.LoadUint32(&utils.Timestamp) expired = expired[:0] s.mux.RLock() for id, v := range s.db { - if v.expiry != 0 && v.expiry < atomic.LoadUint32(&utils.Timestamp) { + if v.expiry != 0 && v.expiry <= ts { expired = append(expired, id) } } s.mux.RUnlock() s.mux.Lock() + // Double-checked locking. + // We might have replaced the item in the meantime. for i := range expired { - delete(s.db, expired[i]) + v := s.db[expired[i]] + if v.expiry != 0 && v.expiry <= ts { + delete(s.db, expired[i]) + } } s.mux.Unlock() } diff --git a/middleware/logger/README.md b/middleware/logger/README.md index c77d4f6a..afb8c16f 100644 --- a/middleware/logger/README.md +++ b/middleware/logger/README.md @@ -12,6 +12,7 @@ Logger middleware for [Fiber](https://github.com/gofiber/fiber) that logs HTTP r - [Changing TimeZone & TimeFormat](#changing-timezone--timeformat) - [Custom File Writer](#custom-file-writer) - [Logging with Zerolog](#logging-with-zerolog) + - [Add Custom Tags](#add-custom-tags) - [Config](#config) - [Default Config](#default-config-1) - [Constants](#constants) @@ -40,7 +41,7 @@ app.Use(logger.New()) ```go app.Use(logger.New(logger.Config{ - Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", + Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", })) ``` @@ -76,6 +77,30 @@ app.Use(logger.New(logger.Config{ Output: file, })) ``` +### Add Custom Tags +```go +app.Use(logger.New(logger.Config{ + CustomTags: map[string]logger.LogFunc{ + "custom_tag": func(output logger.Buffer, c *fiber.Ctx, data *logger.Data, extraParam string) (int, error) { + return output.WriteString("it is a custom tag") + }, + }, +})) +``` + +### Callback after log is written + +```go +app.Use(logger.New(logger.Config{ + TimeFormat: time.RFC3339Nano, + TimeZone: "Asia/Shanghai", + Done: func(c *fiber.Ctx, logString []byte) { + if c.Response().StatusCode() != fiber.StatusOK { + reporter.SendToSlack(logString) + } + }, +})) +``` ### Logging with Zerolog ```go @@ -123,6 +148,17 @@ type Config struct { // Optional. Default: nil Next func(c fiber.Ctx) bool + // Done is a function that is called after the log string for a request is written to Output, + // and pass the log string as parameter. + // + // Optional. Default: nil + Done func(c *fiber.Ctx, logString []byte) + + // tagFunctions defines the custom tag action + // + // Optional. Default: map[string]LogFunc + CustomTags map[string]LogFunc + // Format defines the logging tags // // Optional. Default: [${time}] ${status} - ${latency} ${method} ${path}\n @@ -145,7 +181,7 @@ type Config struct { // Output is a writer where logs are written // - // Default: os.Stderr + // Default: os.Stdout Output io.Writer // You can define specific things before the returning the handler: colors, template, etc. @@ -160,6 +196,8 @@ type Config struct { // Optional. Default: defaultLogger LoggerFunc func(c fiber.Ctx, data *LoggerData, cfg Config) error } + +type LogFunc func(buf logger.Buffer, c *fiber.Ctx, data *logger.Data, extraParam string) (int, error) ``` ## Default Config @@ -167,6 +205,7 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ Next: nil, + Done: nil, Format: defaultFormat, TimeFormat: "15:04:05", TimeZone: "Local", diff --git a/middleware/logger/config.go b/middleware/logger/config.go index 700f2c69..a30f3cd2 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -16,6 +16,17 @@ type Config struct { // Optional. Default: nil Next func(c fiber.Ctx) bool + // Done is a function that is called after the log string for a request is written to Output, + // and pass the log string as parameter. + // + // Optional. Default: nil + Done func(c fiber.Ctx, logString []byte) + + // tagFunctions defines the custom tag action + // + // Optional. Default: map[string]LogFunc + CustomTags map[string]LogFunc + // Format defines the logging tags // // Optional. Default: [${time}] ${status} - ${latency} ${method} ${path}\n @@ -51,23 +62,45 @@ type Config struct { // If you don't define anything for this field, it'll use default logger of Fiber. // // Optional. Default: defaultLogger - LoggerFunc func(c fiber.Ctx, data *LoggerData, cfg Config) error + LoggerFunc func(c fiber.Ctx, data *Data, cfg Config) error enableColors bool enableLatency bool timeZoneLocation *time.Location } +const ( + startTag = "${" + endTag = "}" + paramSeparator = ":" +) + +type Buffer interface { + Len() int + ReadFrom(r io.Reader) (int64, error) + WriteTo(w io.Writer) (int64, error) + Bytes() []byte + Write(p []byte) (int, error) + WriteByte(c byte) error + WriteString(s string) (int, error) + Set(p []byte) + SetString(s string) + String() string +} + +type LogFunc func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) + // ConfigDefault is the default config var ConfigDefault = Config{ Next: nil, + Done: nil, Format: defaultFormat, TimeFormat: "15:04:05", TimeZone: "Local", TimeInterval: 500 * time.Millisecond, Output: os.Stdout, BeforeHandlerFunc: beforeHandlerFunc, - LoggerFunc: defaultLogger, + LoggerFunc: defaultLoggerInstance, enableColors: true, } @@ -75,11 +108,8 @@ var ConfigDefault = Config{ var defaultFormat = "[${time}] ${status} - ${latency} ${method} ${path}\n" // Function to check if the logger format is compatible for coloring -func validCustomFormat(format string) bool { +func checkColorEnable(format string) bool { validTemplates := []string{"${status}", "${method}"} - if format == "" { - return true - } for _, template := range validTemplates { if strings.Contains(format, template) { return true @@ -98,18 +128,17 @@ func configDefault(config ...Config) Config { // Override default config cfg := config[0] - // Enable colors if no custom format or output is given - if validCustomFormat(cfg.Format) && cfg.Output == nil { - cfg.enableColors = true - } - // Set default values if cfg.Next == nil { cfg.Next = ConfigDefault.Next } + if cfg.Done == nil { + cfg.Done = ConfigDefault.Done + } if cfg.Format == "" { cfg.Format = ConfigDefault.Format } + if cfg.TimeZone == "" { cfg.TimeZone = ConfigDefault.TimeZone } @@ -131,5 +160,10 @@ func configDefault(config ...Config) Config { cfg.LoggerFunc = ConfigDefault.LoggerFunc } + // Enable colors if no custom format or output is given + if cfg.Output == nil && checkColorEnable(cfg.Format) { + cfg.enableColors = true + } + return cfg } diff --git a/middleware/logger/data.go b/middleware/logger/data.go new file mode 100644 index 00000000..504d08ca --- /dev/null +++ b/middleware/logger/data.go @@ -0,0 +1,21 @@ +package logger + +import ( + "sync" + "sync/atomic" + "time" +) + +var DataPool = sync.Pool{New: func() interface{} { return new(Data) }} + +// Data is a struct to define some variables to use in custom logger function. +type Data struct { + Pid string + ErrPaddingStr string + ChainErr error + TemplateChain [][]byte + LogFuncChain []LogFunc + Start time.Time + Stop time.Time + Timestamp atomic.Value +} diff --git a/middleware/logger/default_logger.go b/middleware/logger/default_logger.go index 7565f3dc..624725ed 100644 --- a/middleware/logger/default_logger.go +++ b/middleware/logger/default_logger.go @@ -2,42 +2,31 @@ package logger import ( "fmt" - "io" "os" - "strings" "sync" - "sync/atomic" "time" + "github.com/gofiber/fiber/v2/utils" "github.com/gofiber/fiber/v3" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" - "github.com/valyala/fasttemplate" ) -// LoggerData is a struct to define some variables to use in custom logger function. -type LoggerData struct { - mu sync.Mutex - Pid string - ErrPaddingStr string - ChainErr error - Start time.Time - Stop time.Time - Timestamp atomic.Value -} - -var tmpl *fasttemplate.Template +var mu sync.Mutex // default logger for fiber -func defaultLogger(c fiber.Ctx, data *LoggerData, cfg Config) error { +func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) 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 @@ -60,131 +49,34 @@ func defaultLogger(c fiber.Ctx, data *LoggerData, cfg Config) error { // Write buffer to output _, _ = cfg.Output.Write(buf.Bytes()) - // Put buffer back to pool - bytebufferpool.Put(buf) + if cfg.Done != nil { + cfg.Done(c, buf.Bytes()) + } // End chain return nil } - // Loop over template tags to replace it with the correct value - _, err := tmpl.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { - switch tag { - case TagTime: - return buf.WriteString(data.Timestamp.Load().(string)) - case TagReferer: - return buf.WriteString(c.Get(fiber.HeaderReferer)) - case TagProtocol: - return buf.WriteString(c.Protocol()) - case TagScheme: - return buf.WriteString(c.Scheme()) - case TagPid: - return buf.WriteString(data.Pid) - case TagPort: - return buf.WriteString(c.Port()) - case TagIP: - return buf.WriteString(c.IP()) - case TagIPs: - return buf.WriteString(c.Get(fiber.HeaderXForwardedFor)) - case TagHost: - return buf.WriteString(c.Host()) - case TagPath: - return buf.WriteString(c.Path()) - case TagURL: - return buf.WriteString(c.OriginalURL()) - case TagUA: - return buf.WriteString(c.Get(fiber.HeaderUserAgent)) - case TagLatency: - return buf.WriteString(fmt.Sprintf("%7v", data.Stop.Sub(data.Start).Round(time.Millisecond))) - case TagBody: - return buf.Write(c.Body()) - case TagBytesReceived: - return appendInt(buf, len(c.Request().Body())) - case TagBytesSent: - return appendInt(buf, len(c.Response().Body())) - case TagRoute: - return buf.WriteString(c.Route().Path) - case TagStatus: - if cfg.enableColors { - return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset)) - } - return appendInt(buf, c.Response().StatusCode()) - case TagResBody: - return buf.Write(c.Response().Body()) - case TagReqHeaders: - out := make(map[string]string, 0) - if err := c.Bind().Header(&out); err != nil { - return 0, err - } - - reqHeaders := make([]string, 0) - for k, v := range out { - reqHeaders = append(reqHeaders, k+"="+v) - } - return buf.Write([]byte(strings.Join(reqHeaders, "&"))) - case TagQueryStringParams: - return buf.WriteString(c.Request().URI().QueryArgs().String()) - case TagMethod: - if cfg.enableColors { - return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset)) - } - return buf.WriteString(c.Method()) - case TagBlack: - return buf.WriteString(colors.Black) - case TagRed: - return buf.WriteString(colors.Red) - case TagGreen: - return buf.WriteString(colors.Green) - case TagYellow: - return buf.WriteString(colors.Yellow) - case TagBlue: - return buf.WriteString(colors.Blue) - case TagMagenta: - return buf.WriteString(colors.Magenta) - case TagCyan: - return buf.WriteString(colors.Cyan) - case TagWhite: - return buf.WriteString(colors.White) - case TagReset: - return buf.WriteString(colors.Reset) - case TagError: - if data.ChainErr != nil { - return buf.WriteString(data.ChainErr.Error()) - } - return buf.WriteString("-") - default: - // Check if we have a value tag i.e.: "reqHeader:x-key" - switch { - case strings.HasPrefix(tag, TagReqHeader): - return buf.WriteString(c.Get(tag[10:])) - case strings.HasPrefix(tag, TagRespHeader): - return buf.WriteString(c.GetRespHeader(tag[11:])) - case strings.HasPrefix(tag, TagQuery): - return buf.WriteString(c.Query(tag[6:])) - case strings.HasPrefix(tag, TagForm): - return buf.WriteString(c.FormValue(tag[5:])) - case strings.HasPrefix(tag, TagCookie): - return buf.WriteString(c.Cookies(tag[7:])) - case strings.HasPrefix(tag, TagLocals): - switch v := c.Locals(tag[7:]).(type) { - case []byte: - return buf.Write(v) - case string: - return buf.WriteString(v) - case nil: - return 0, nil - default: - return buf.WriteString(fmt.Sprintf("%v", v)) - } - } + 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]) + } else if data.TemplateChain[i] == nil { + _, err = logFunc(buf, c, data, "") + } else { + _, err = logFunc(buf, c, data, utils.UnsafeString(data.TemplateChain[i])) } - return 0, nil - }) + if err != nil { + break + } + } + // Also write errors to the buffer if err != nil { _, _ = buf.WriteString(err.Error()) } - data.mu.Lock() + mu.Lock() // Write buffer to output if _, err := cfg.Output.Write(buf.Bytes()); err != nil { // Write error to output @@ -193,18 +85,17 @@ func defaultLogger(c fiber.Ctx, data *LoggerData, cfg Config) error { fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) } } - data.mu.Unlock() - // Put buffer back to pool - bytebufferpool.Put(buf) + mu.Unlock() + + if cfg.Done != nil { + cfg.Done(c, buf.Bytes()) + } return nil } // run something before returning the handler func beforeHandlerFunc(cfg Config) { - // Create template parser - tmpl = fasttemplate.New(cfg.Format, "${", "}") - // If colors are enabled, check terminal compatibility if cfg.enableColors { cfg.Output = colorable.NewColorableStdout() @@ -214,8 +105,8 @@ func beforeHandlerFunc(cfg Config) { } } -func appendInt(buf *bytebufferpool.ByteBuffer, v int) (int, error) { - old := len(buf.B) - buf.B = fasthttp.AppendUint(buf.B, v) - return len(buf.B) - old, nil +func appendInt(output Buffer, v int) (int, error) { + old := output.Len() + output.Set(fasthttp.AppendUint(output.Bytes(), v)) + return output.Len() - old, nil } diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 2bd55dc7..489c52f5 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -11,48 +11,6 @@ import ( "github.com/gofiber/fiber/v3" ) -// Logger variables -const ( - TagPid = "pid" - TagTime = "time" - TagReferer = "referer" - TagProtocol = "protocol" - TagScheme = "scheme" - TagPort = "port" - TagIP = "ip" - TagIPs = "ips" - TagHost = "host" - TagMethod = "method" - TagPath = "path" - TagURL = "url" - TagUA = "ua" - TagLatency = "latency" - TagStatus = "status" - TagResBody = "resBody" - TagReqHeaders = "reqHeaders" - TagQueryStringParams = "queryParams" - TagBody = "body" - TagBytesSent = "bytesSent" - TagBytesReceived = "bytesReceived" - TagRoute = "route" - TagError = "error" - TagReqHeader = "reqHeader:" - TagRespHeader = "respHeader:" - TagLocals = "locals:" - TagQuery = "query:" - TagForm = "form:" - TagCookie = "cookie:" - TagBlack = "black" - TagRed = "red" - TagGreen = "green" - TagYellow = "yellow" - TagBlue = "blue" - TagMagenta = "magenta" - TagCyan = "cyan" - TagWhite = "white" - TagReset = "reset" -) - // New creates a new middleware handler func New(config ...Config) fiber.Handler { // Set default config @@ -67,14 +25,14 @@ func New(config ...Config) fiber.Handler { } // Check if format contains latency - cfg.enableLatency = strings.Contains(cfg.Format, "${latency}") + cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}") - // Create correct timeformat var timestamp atomic.Value + // Create correct timeformat timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat)) // Update date/time every 500 milliseconds in a separate go routine - if strings.Contains(cfg.Format, "${time}") { + if strings.Contains(cfg.Format, "${"+TagTime+"}") { go func() { for { time.Sleep(cfg.TimeInterval) @@ -88,7 +46,6 @@ func New(config ...Config) fiber.Handler { // Set variables var ( - mu sync.Mutex once sync.Once errHandler fiber.ErrorHandler ) @@ -101,10 +58,11 @@ func New(config ...Config) fiber.Handler { cfg.BeforeHandlerFunc(cfg) // Logger data - data := &LoggerData{ - Pid: pid, - ErrPaddingStr: errPaddingStr, - Timestamp: timestamp, + // instead of analyzing the template inside(handler) each time, this is done once before + // and we create several slices of the same length with the functions to be executed and fixed parts. + templateChain, logFunChain, err := buildLogFuncChain(&cfg, createTagMap(&cfg)) + if err != nil { + panic(err) } // Return new handler @@ -130,16 +88,26 @@ func New(config ...Config) fiber.Handler { errHandler = c.App().ErrorHandler }) - var start, stop time.Time + // Logger data + data := DataPool.Get().(*Data) + // no need for a reset, as long as we always override everything + data.Pid = pid + data.ErrPaddingStr = errPaddingStr + data.Timestamp = timestamp + data.TemplateChain = templateChain + data.LogFuncChain = logFunChain + // put data back in the pool + defer DataPool.Put(data) // Set latency start time if cfg.enableLatency { - start = time.Now() + data.Start = time.Now() } // Handle request, store err for logging chainErr := c.Next() + data.ChainErr = chainErr // Manually call error handler if chainErr != nil { if err := errHandler(c, chainErr); err != nil { @@ -149,19 +117,13 @@ func New(config ...Config) fiber.Handler { // Set latency stop time if cfg.enableLatency { - stop = time.Now() + data.Stop = time.Now() } // Logger instance & update some logger data fields - mu.Lock() - data.ChainErr = chainErr - data.Start = start - data.Stop = stop - if err = cfg.LoggerFunc(c, data, cfg); err != nil { return err } - mu.Unlock() return nil } diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 243c80c6..76f485b6 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -1,6 +1,7 @@ package logger import ( + "bytes" "errors" "fmt" "io" @@ -99,6 +100,27 @@ func Test_Logger_Next(t *testing.T) { require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } +// go test -run Test_Logger_Done +func Test_Logger_Done(t *testing.T) { + 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) + } + }, + })).Get("/logging", func(ctx fiber.Ctx) error { + return ctx.SendStatus(fiber.StatusOK) + }) + + resp, err := app.Test(httptest.NewRequest("GET", "/logging", nil)) + + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.True(t, buf.Len() > 0) +} + // go test -run Test_Logger_ErrorTimeZone func Test_Logger_ErrorTimeZone(t *testing.T) { app := fiber.New() @@ -140,7 +162,7 @@ func Test_Logger_All(t *testing.T) { app := fiber.New() app.Use(New(Config{ - Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${header:test}${query:test}${form:test}${cookie:test}${non}", + Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}", Output: buf, })) @@ -264,30 +286,59 @@ func Test_Logger_Data_Race(t *testing.T) { // go test -v -run=^$ -bench=Benchmark_Logger -benchmem -count=4 func Benchmark_Logger(b *testing.B) { - app := fiber.New() + benchSetup := func(bb *testing.B, app *fiber.App) { + h := app.Handler() - app.Use(New(Config{ - Format: "${bytesReceived} ${bytesSent} ${status}", - Output: io.Discard, - })) - app.Get("/", func(c fiber.Ctx) error { - return c.SendString("Hello, World!") - }) + fctx := &fasthttp.RequestCtx{} + fctx.Request.Header.SetMethod("GET") + fctx.Request.SetRequestURI("/") - h := app.Handler() + bb.ReportAllocs() + bb.ResetTimer() - fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") - fctx.Request.SetRequestURI("/") + for n := 0; n < bb.N; n++ { + h(fctx) + } - b.ReportAllocs() - b.ResetTimer() - - 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) { + app := fiber.New() + app.Use(New(Config{ + Format: "${bytesReceived} ${bytesSent} ${status}", + Output: io.Discard, + })) + app.Get("/", func(c fiber.Ctx) error { + c.Set("test", "test") + return c.SendString("Hello, World!") + }) + benchSetup(bb, app) + }) + + b.Run("DefaultFormat", func(bb *testing.B) { + app := fiber.New() + app.Use(New(Config{ + Output: io.Discard, + })) + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Hello, World!") + }) + benchSetup(bb, app) + }) + + b.Run("WithTagParameter", func(bb *testing.B) { + app := fiber.New() + app.Use(New(Config{ + Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}", + Output: io.Discard, + })) + app.Get("/", func(c fiber.Ctx) error { + c.Set("test", "test") + return c.SendString("Hello, World!") + }) + benchSetup(bb, app) + }) } // go test -run Test_Response_Header @@ -360,3 +411,32 @@ func Test_ReqHeader_Header(t *testing.T) { require.Equal(t, fiber.StatusOK, resp.StatusCode) require.Equal(t, "Hello fiber!", buf.String()) } + +// go test -run Test_CustomTags +func Test_CustomTags(t *testing.T) { + customTag := "it is a custom tag" + + buf := bytebufferpool.Get() + defer bytebufferpool.Put(buf) + + app := fiber.New() + app.Use(New(Config{ + Format: "${custom_tag}", + CustomTags: map[string]LogFunc{ + "custom_tag": func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(customTag) + }, + }, + Output: buf, + })) + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Hello fiber!") + }) + reqHeaderReq := httptest.NewRequest("GET", "/", nil) + reqHeaderReq.Header.Add("test", "Hello fiber!") + resp, err := app.Test(reqHeaderReq) + + require.NoError(t, err) + require.Equal(t, fiber.StatusOK, resp.StatusCode) + require.Equal(t, customTag, buf.String()) +} diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go new file mode 100644 index 00000000..feb473ad --- /dev/null +++ b/middleware/logger/tags.go @@ -0,0 +1,211 @@ +package logger + +import ( + "fmt" + "strings" + "time" + + "github.com/gofiber/fiber/v3" +) + +// Logger variables +const ( + TagPid = "pid" + TagTime = "time" + TagReferer = "referer" + TagProtocol = "protocol" + TagScheme = "scheme" + TagPort = "port" + TagIP = "ip" + TagIPs = "ips" + TagHost = "host" + TagMethod = "method" + TagPath = "path" + TagURL = "url" + TagUA = "ua" + TagLatency = "latency" + TagStatus = "status" + TagResBody = "resBody" + TagReqHeaders = "reqHeaders" + TagQueryStringParams = "queryParams" + TagBody = "body" + TagBytesSent = "bytesSent" + TagBytesReceived = "bytesReceived" + TagRoute = "route" + TagError = "error" + TagReqHeader = "reqHeader:" + TagRespHeader = "respHeader:" + TagLocals = "locals:" + TagQuery = "query:" + TagForm = "form:" + TagCookie = "cookie:" + TagBlack = "black" + TagRed = "red" + TagGreen = "green" + TagYellow = "yellow" + TagBlue = "blue" + TagMagenta = "magenta" + TagCyan = "cyan" + TagWhite = "white" + TagReset = "reset" +) + +// createTagMap function merged the default with the custom tags +func createTagMap(cfg *Config) map[string]LogFunc { + // Set default tags + tagFunctions := map[string]LogFunc{ + TagReferer: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Get(fiber.HeaderReferer)) + }, + TagProtocol: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Protocol()) + }, + TagScheme: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Scheme()) + }, + TagPort: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Port()) + }, + TagIP: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.IP()) + }, + TagIPs: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Get(fiber.HeaderXForwardedFor)) + }, + TagHost: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Hostname()) + }, + TagPath: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Path()) + }, + TagURL: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.OriginalURL()) + }, + TagUA: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Get(fiber.HeaderUserAgent)) + }, + TagBody: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.Write(c.Body()) + }, + TagBytesReceived: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return appendInt(output, len(c.Request().Body())) + }, + TagBytesSent: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return appendInt(output, len(c.Response().Body())) + }, + TagRoute: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Route().Path) + }, + TagResBody: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.Write(c.Response().Body()) + }, + TagReqHeaders: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + out := make(map[string]string, 0) + if err := c.Bind().Header(&out); err != nil { + return 0, err + } + + reqHeaders := make([]string, 0) + for k, v := range out { + reqHeaders = append(reqHeaders, k+"="+v) + } + return output.Write([]byte(strings.Join(reqHeaders, "&"))) + }, + TagQueryStringParams: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Request().URI().QueryArgs().String()) + }, + + TagBlack: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Black) + }, + TagRed: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Red) + }, + TagGreen: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Green) + }, + TagYellow: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Yellow) + }, + TagBlue: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Blue) + }, + TagMagenta: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Magenta) + }, + TagCyan: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Cyan) + }, + TagWhite: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.White) + }, + TagReset: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.App().Config().ColorScheme.Reset) + }, + TagError: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + if data.ChainErr != nil { + return output.WriteString(data.ChainErr.Error()) + } + return output.WriteString("-") + }, + TagReqHeader: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Get(extraParam)) + }, + TagRespHeader: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.GetRespHeader(extraParam)) + }, + TagQuery: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Query(extraParam)) + }, + TagForm: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.FormValue(extraParam)) + }, + TagCookie: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + return output.WriteString(c.Cookies(extraParam)) + }, + TagLocals: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + switch v := c.Locals(extraParam).(type) { + case []byte: + return output.Write(v) + case string: + return output.WriteString(v) + case nil: + return 0, nil + default: + return output.WriteString(fmt.Sprintf("%v", v)) + } + }, + TagStatus: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + if cfg.enableColors { + colors := c.App().Config().ColorScheme + return output.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset)) + } + return appendInt(output, c.Response().StatusCode()) + }, + TagMethod: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + if cfg.enableColors { + colors := c.App().Config().ColorScheme + return output.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset)) + } + return output.WriteString(c.Method()) + }, + TagPid: func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (int, error) { + 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) + 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)) + }, + } + // merge with custom tags from user + if cfg.CustomTags != nil { + for k, v := range cfg.CustomTags { + tagFunctions[k] = v + } + } + + return tagFunctions +} diff --git a/middleware/logger/template_chain.go b/middleware/logger/template_chain.go new file mode 100644 index 00000000..ceb31a33 --- /dev/null +++ b/middleware/logger/template_chain.go @@ -0,0 +1,67 @@ +package logger + +import ( + "bytes" + "errors" + + "github.com/gofiber/fiber/v2/utils" +) + +// buildLogFuncChain analyzes the template and creates slices with the functions for execution and +// slices with the fixed parts of the template and the parameters +// +// fixParts contains the fixed parts of the template or parameters if a function is stored in the funcChain at this position +// 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) { + // 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) + + for { + currentPos := bytes.Index(templateB, startTagB) + if currentPos < 0 { + // no starting tag found in the existing template part + break + } + // add fixed part + funcChain = append(funcChain, nil) + fixParts = append(fixParts, templateB[:currentPos]) + + templateB = templateB[currentPos+len(startTagB):] + currentPos = bytes.Index(templateB, endTagB) + if currentPos < 0 { + // cannot find end tag - just write it to the output. + funcChain = append(funcChain, nil) + fixParts = append(fixParts, startTagB) + break + } + // ## 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 { + return nil, nil, errors.New("No parameter found in \"" + utils.UnsafeString(templateB[:currentPos]) + "\"") + } + } else if logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:currentPos])]; ok { + // add functions without parameter + funcChain = append(funcChain, logFunc) + fixParts = append(fixParts, nil) + } + // ## function block end ## + + // reduce the template string + templateB = templateB[currentPos+len(endTagB):] + } + // set the rest + funcChain = append(funcChain, nil) + fixParts = append(fixParts, templateB) + + return +} diff --git a/middleware/pprof/README.md b/middleware/pprof/README.md index 4e576e9a..ec094e60 100644 --- a/middleware/pprof/README.md +++ b/middleware/pprof/README.md @@ -24,6 +24,16 @@ After you initiate your Fiber app, you can use the following possibilities: app.Use(pprof.New()) ``` +In systems where you have multiple ingress endpoints, it is common to add a URL prefix, like so: + +```go +// Default middleware +app.Use(pprof.New(pprof.Config{Prefix: "/endpoint-prefix"})) +``` + +This prefix will be added to the default path of "/debug/pprof/", for a resulting URL of: +"/endpoint-prefix/debug/pprof/". + ## Config ```go @@ -32,7 +42,18 @@ type Config struct { // Next defines a function to skip this middleware when returned true. // // Optional. Default: nil +<<<<<<< HEAD Next func(c fiber.Ctx) bool +======= + Next func(c *fiber.Ctx) bool + + // Prefix defines a URL prefix added before "/debug/pprof". + // Note that it should start with (but not end with) a slash. + // Example: "/federated-fiber" + // + // Optional. Default: "" + Prefix string +>>>>>>> origin/master } ``` @@ -42,4 +63,4 @@ type Config struct { var ConfigDefault = Config{ Next: nil, } -``` \ No newline at end of file +``` diff --git a/middleware/pprof/config.go b/middleware/pprof/config.go index bbfa7542..618f5b65 100644 --- a/middleware/pprof/config.go +++ b/middleware/pprof/config.go @@ -8,6 +8,13 @@ type Config struct { // // Optional. Default: nil Next func(c fiber.Ctx) bool + + // Prefix defines a URL prefix added before "/debug/pprof". + // Note that it should start with (but not end with) a slash. + // Example: "/federated-fiber" + // + // Optional. Default: "" + Prefix string } var ConfigDefault = Config{ diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go index afaab440..aa55dc01 100644 --- a/middleware/pprof/pprof.go +++ b/middleware/pprof/pprof.go @@ -37,39 +37,39 @@ func New(config ...Config) fiber.Handler { path := c.Path() // We are only interested in /debug/pprof routes - if len(path) < 12 || !strings.HasPrefix(path, "/debug/pprof") { + if len(path) < 12 || !strings.HasPrefix(path, cfg.Prefix+"/debug/pprof") { return c.Next() } // Switch to original path without stripped slashes switch path { - case "/debug/pprof/": + case cfg.Prefix + "/debug/pprof/": pprofIndex(c.Context()) - case "/debug/pprof/cmdline": + case cfg.Prefix + "/debug/pprof/cmdline": pprofCmdline(c.Context()) - case "/debug/pprof/profile": + case cfg.Prefix + "/debug/pprof/profile": pprofProfile(c.Context()) - case "/debug/pprof/symbol": + case cfg.Prefix + "/debug/pprof/symbol": pprofSymbol(c.Context()) - case "/debug/pprof/trace": + case cfg.Prefix + "/debug/pprof/trace": pprofTrace(c.Context()) - case "/debug/pprof/allocs": + case cfg.Prefix + "/debug/pprof/allocs": pprofAllocs(c.Context()) - case "/debug/pprof/block": + case cfg.Prefix + "/debug/pprof/block": pprofBlock(c.Context()) - case "/debug/pprof/goroutine": + case cfg.Prefix + "/debug/pprof/goroutine": pprofGoroutine(c.Context()) - case "/debug/pprof/heap": + case cfg.Prefix + "/debug/pprof/heap": pprofHeap(c.Context()) - case "/debug/pprof/mutex": + case cfg.Prefix + "/debug/pprof/mutex": pprofMutex(c.Context()) - case "/debug/pprof/threadcreate": + case cfg.Prefix + "/debug/pprof/threadcreate": pprofThreadcreate(c.Context()) default: // pprof index only works with trailing slash if strings.HasSuffix(path, "/") { path = strings.TrimRight(path, "/") } else { - path = "/debug/pprof/" + path = cfg.Prefix + "/debug/pprof/" } return c.Redirect().To(path) diff --git a/middleware/pprof/pprof_test.go b/middleware/pprof/pprof_test.go index eac8b3ca..07d8ecfd 100644 --- a/middleware/pprof/pprof_test.go +++ b/middleware/pprof/pprof_test.go @@ -28,6 +28,24 @@ func Test_Non_Pprof_Path(t *testing.T) { require.Equal(t, "escaped", string(b)) } +func Test_Non_Pprof_Path_WithPrefix(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{Prefix: "/federated-fiber"})) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("escaped") + }) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + require.Equal(t, nil, err) + require.Equal(t, 200, resp.StatusCode) + + b, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, "escaped", string(b)) +} + func Test_Pprof_Index(t *testing.T) { app := fiber.New() @@ -47,6 +65,25 @@ func Test_Pprof_Index(t *testing.T) { require.True(t, bytes.Contains(b, []byte("/debug/pprof/"))) } +func Test_Pprof_Index_WithPrefix(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{Prefix: "/federated-fiber"})) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("escaped") + }) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil)) + require.Equal(t, nil, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType)) + + b, err := io.ReadAll(resp.Body) + require.Equal(t, nil, err) + require.Equal(t, true, bytes.Contains(b, []byte("/debug/pprof/"))) +} + func Test_Pprof_Subs(t *testing.T) { app := fiber.New() @@ -74,6 +111,33 @@ func Test_Pprof_Subs(t *testing.T) { } } +func Test_Pprof_Subs_WithPrefix(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{Prefix: "/federated-fiber"})) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("escaped") + }) + + subs := []string{ + "cmdline", "profile", "symbol", "trace", "allocs", "block", + "goroutine", "heap", "mutex", "threadcreate", + } + + for _, sub := range subs { + t.Run(sub, func(t *testing.T) { + target := "/federated-fiber/debug/pprof/" + sub + if sub == "profile" { + target += "?seconds=1" + } + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, target, nil), 5000) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + }) + } +} + func Test_Pprof_Other(t *testing.T) { app := fiber.New() @@ -88,6 +152,20 @@ func Test_Pprof_Other(t *testing.T) { require.Equal(t, 302, resp.StatusCode) } +func Test_Pprof_Other_WithPrefix(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{Prefix: "/federated-fiber"})) + + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("escaped") + }) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/302", nil)) + require.Equal(t, nil, err) + require.Equal(t, 302, resp.StatusCode) +} + // go test -run Test_Pprof_Next func Test_Pprof_Next(t *testing.T) { t.Parallel() @@ -104,3 +182,21 @@ func Test_Pprof_Next(t *testing.T) { require.NoError(t, err) require.Equal(t, 404, resp.StatusCode) } + +// go test -run Test_Pprof_Next_WithPrefix +func Test_Pprof_Next_WithPrefix(t *testing.T) { + t.Parallel() + + app := fiber.New() + + app.Use(New(Config{ + Next: func(_ fiber.Ctx) bool { + return true + }, + Prefix: "/federated-fiber", + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil)) + require.Equal(t, nil, err) + require.Equal(t, 404, resp.StatusCode) +} diff --git a/middleware/recover/config.go b/middleware/recover/config.go index 569516cb..c16a106d 100644 --- a/middleware/recover/config.go +++ b/middleware/recover/config.go @@ -22,8 +22,6 @@ type Config struct { StackTraceHandler func(c fiber.Ctx, e any) } -var defaultStackTraceBufLen = 1024 - // ConfigDefault is the default config var ConfigDefault = Config{ Next: nil, diff --git a/middleware/recover/recover.go b/middleware/recover/recover.go index c51c1c19..20bccca4 100644 --- a/middleware/recover/recover.go +++ b/middleware/recover/recover.go @@ -3,15 +3,13 @@ package recover import ( "fmt" "os" - "runtime" + "runtime/debug" "github.com/gofiber/fiber/v3" ) -func defaultStackTraceHandler(c fiber.Ctx, e any) { - buf := make([]byte, defaultStackTraceBufLen) - buf = buf[:runtime.Stack(buf, false)] - _, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, buf)) +func defaultStackTraceHandler(_ fiber.Ctx, e interface{}) { + _, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, debug.Stack())) } // New creates a new middleware handler diff --git a/mount.go b/mount.go index ccb25eca..83b9229a 100644 --- a/mount.go +++ b/mount.go @@ -44,8 +44,10 @@ func (app *App) mount(prefix string, fiber *App) Router { // Support for configs of mounted-apps and sub-mounted-apps for mountedPrefixes, subApp := range fiber.mountFields.appList { - subApp.mountFields.mountPath = prefix + mountedPrefixes - app.mountFields.appList[prefix+mountedPrefixes] = subApp + path := getGroupPath(prefix, mountedPrefixes) + + subApp.mountFields.mountPath = path + app.mountFields.appList[path] = subApp } // Execute onMount hooks @@ -70,8 +72,10 @@ func (grp *Group) mount(prefix string, fiber *App) Router { // Support for configs of mounted-apps and sub-mounted-apps for mountedPrefixes, subApp := range fiber.mountFields.appList { - subApp.mountFields.mountPath = groupPath + mountedPrefixes - grp.app.mountFields.appList[groupPath+mountedPrefixes] = subApp + path := getGroupPath(groupPath, mountedPrefixes) + + subApp.mountFields.mountPath = path + grp.app.mountFields.appList[path] = subApp } // Execute onMount hooks @@ -107,7 +111,7 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { } if len(parent) > 0 { - prefix = parent[0] + prefix + prefix = getGroupPath(parent[0], prefix) } if _, ok := app.mountFields.appList[prefix]; !ok { @@ -131,7 +135,7 @@ func (app *App) addSubAppsRoutes(appList map[string]*App, parent ...string) { } if len(parent) > 0 { - prefix = parent[0] + prefix + prefix = getGroupPath(parent[0], prefix) } // add routes diff --git a/mount_test.go b/mount_test.go index a48f0376..6167af0f 100644 --- a/mount_test.go +++ b/mount_test.go @@ -28,6 +28,25 @@ func Test_App_Mount(t *testing.T) { require.Equal(t, uint32(1), app.handlersCount) } +func Test_App_Mount_RootPath_Nested(t *testing.T) { + app := New() + dynamic := New() + apiserver := New() + + apiroutes := apiserver.Group("/v1") + apiroutes.Get("/home", func(c Ctx) error { + return c.SendString("home") + }) + + dynamic.Use("/api", apiserver) + app.Use("/", dynamic) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/v1/home", nil)) + require.NoError(t, err, "app.Test(req)") + require.Equal(t, 200, resp.StatusCode, "Status code") + require.Equal(t, uint32(2), app.handlersCount) +} + // go test -run Test_App_Mount_Nested func Test_App_Mount_Nested(t *testing.T) { app := New() @@ -138,6 +157,24 @@ func Test_App_Group_Mount(t *testing.T) { require.Equal(t, uint32(1), app.handlersCount) } +func Test_App_UseParentErrorHandler(t *testing.T) { + app := New(Config{ + ErrorHandler: func(ctx Ctx, err error) error { + return ctx.Status(500).SendString("hi, i'm a custom error") + }, + }) + + fiber := New() + fiber.Get("/", func(c Ctx) error { + return errors.New("something happened") + }) + + app.Use("/api", fiber) + + resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil)) + testErrorResponse(t, err, resp, "hi, i'm a custom error") +} + func Test_App_UseMountedErrorHandler(t *testing.T) { app := New() diff --git a/path.go b/path.go index ab96b36a..8936f904 100644 --- a/path.go +++ b/path.go @@ -437,10 +437,12 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma // take over the params positions params[paramsIterator] = path[:i] - // check constraint - for _, c := range segment.Constraints { - if matched := c.CheckConstraint(params[paramsIterator]); !matched { - return false + if !(segment.IsOptional && i == 0) { + // check constraint + for _, c := range segment.Constraints { + if matched := c.CheckConstraint(params[paramsIterator]); !matched { + return false + } } } diff --git a/path_test.go b/path_test.go index 6ebf5562..b9ee575c 100644 --- a/path_test.go +++ b/path_test.go @@ -598,6 +598,12 @@ func Test_Path_matchParams(t *testing.T) { {url: "/api/v1/2005-1101/paach", params: nil, match: false}, {url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true}, }) + testCase("/api/v1/:param?", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + {url: "/api/v1/", params: []string{""}, match: true}, + }) } func Test_Utils_GetTrimmedParam(t *testing.T) { @@ -852,4 +858,10 @@ func Benchmark_Path_matchParams(t *testing.B) { {url: "/api/v1/2005-1101/paach", params: nil, match: false}, {url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true}, }) + benchCase("/api/v1/:param?", []testparams{ + {url: "/api/v1/entity", params: nil, match: false}, + {url: "/api/v1/8728382", params: []string{"8728382"}, match: true}, + {url: "/api/v1/true", params: nil, match: false}, + {url: "/api/v1/", params: []string{""}, match: true}, + }) } diff --git a/register.go b/register.go index 1f4198b3..60bc19ba 100644 --- a/register.go +++ b/register.go @@ -48,14 +48,14 @@ type Registering struct { // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... func (r *Registering) All(handler Handler, middleware ...Handler) Register { - r.app.register([]string{methodUse}, r.path, handler, middleware...) + r.app.register([]string{methodUse}, r.path, nil, handler, middleware...) return r } // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. func (r *Registering) Get(handler Handler, middleware ...Handler) Register { - r.app.Add([]string{MethodHead}, r.path, handler, middleware...).Add([]string{MethodGet}, r.path, handler, middleware...) + r.app.Add([]string{MethodGet}, r.path, handler, middleware...) return r } @@ -108,7 +108,7 @@ func (r *Registering) Patch(handler Handler, middleware ...Handler) Register { // Add allows you to specify multiple HTTP methods to register a route. func (r *Registering) Add(methods []string, handler Handler, middleware ...Handler) Register { - r.app.register(methods, r.path, handler, middleware...) + r.app.register(methods, r.path, nil, handler, middleware...) return r } diff --git a/router.go b/router.go index 87384976..7f88c7b9 100644 --- a/router.go +++ b/router.go @@ -50,6 +50,7 @@ type Route struct { root bool // Path equals '/' path string // Prettified path routeParser routeParser // Parameter parser + group *Group // Group instance. used for routes in groups // Public fields Method string `json:"method"` // HTTP method @@ -136,7 +137,7 @@ func (app *App) nextCustom(c CustomCtx) (match bool, err error) { // If no match, scan stack again if other methods match the request // Moved from app.handler because middleware may break the route chain - if !c.getMatched() && methodExistCustom(c) { + if !c.getMatched() && app.methodExistCustom(c) { err = ErrMethodNotAllowed } return @@ -184,7 +185,7 @@ func (app *App) next(c *DefaultCtx) (match bool, err error) { // 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 && methodExist(c) { + if !c.matched && app.methodExist(c) { err = ErrMethodNotAllowed } return @@ -202,7 +203,7 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) { defer app.ReleaseCtx(c) // handle invalid http method directly - if methodInt(c.Method()) == -1 { + if app.methodInt(c.Method()) == -1 { _ = c.SendStatus(StatusNotImplemented) return } @@ -266,7 +267,7 @@ func (app *App) copyRoute(route *Route) *Route { } } -func (app *App) register(methods []string, pathRaw string, handler Handler, middleware ...Handler) Router { +func (app *App) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) Router { handlers := middleware if handler != nil { handlers = append(handlers, handler) @@ -277,7 +278,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd // Uppercase HTTP methods method = utils.ToUpper(method) // Check if the HTTP method is valid unless it's USE - if method != methodUse && methodInt(method) == -1 { + if method != methodUse && app.methodInt(method) == -1 { panic(fmt.Sprintf("add: invalid http method %s\n", method)) } // A route requires atleast one ctx handler @@ -324,6 +325,9 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd routeParser: parsedPretty, Params: parsedRaw.params, + // Group data + group: group, + // Public data Path: pathRaw, Method: method, @@ -335,7 +339,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd // Middleware route matches all HTTP methods if isUse { // Add route to all HTTP methods stack - for _, m := range intMethod { + for _, m := range app.config.RequestMethods { // Create a route copy to avoid duplicates during compression r := route app.addRoute(m, &r) @@ -496,7 +500,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) { } // Get unique HTTP method identifier - m := methodInt(method) + m := app.methodInt(method) // prevent identically route registration l := len(app.stack[m]) @@ -530,7 +534,7 @@ func (app *App) buildTree() *App { } // loop all the methods and stacks and create the prefix tree - for m := range intMethod { + for m := range app.config.RequestMethods { tsMap := make(map[string][]*Route) for _, route := range app.stack[m] { treePath := "" @@ -544,7 +548,7 @@ func (app *App) buildTree() *App { } // loop the methods and tree stacks and add global stack and sort everything - for m := range intMethod { + for m := range app.config.RequestMethods { tsMap := app.treeStack[m] for treePart := range tsMap { if treePart != "" { diff --git a/router_test.go b/router_test.go index 8b34b44d..613bb5d7 100644 --- a/router_test.go +++ b/router_test.go @@ -281,7 +281,7 @@ func Test_Router_Register_Missing_Handler(t *testing.T) { require.Equal(t, "missing handler/middleware in route: /doe\n", fmt.Sprintf("%v", err)) } }() - app.register([]string{"USE"}, "/doe", nil) + app.register([]string{"USE"}, "/doe", nil, nil) } func Test_Ensure_Router_Interface_Implementation(t *testing.T) {