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
-## ⭐️ 星星数增长情况
+## ⭐️ Star 数增长情况
@@ -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("