Merge remote-tracking branch 'origin/master' into v3-beta

pull/2255/head
Muhammed Efe Çetin 2022-11-27 20:34:48 +03:00
commit c62dd16e8d
No known key found for this signature in database
GPG Key ID: 0AA4D45CBAA86F73
53 changed files with 1434 additions and 704 deletions

17
.github/README.md vendored
View File

@ -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)

17
.github/README_ckb.md vendored
View File

@ -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)

17
.github/README_de.md vendored
View File

@ -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)

17
.github/README_es.md vendored
View File

@ -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)

17
.github/README_fa.md vendored
View File

@ -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)

17
.github/README_fr.md vendored
View File

@ -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)

17
.github/README_he.md vendored
View File

@ -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)
</div>

17
.github/README_id.md vendored
View File

@ -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)

17
.github/README_it.md vendored
View File

@ -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)

17
.github/README_ja.md vendored
View File

@ -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)

19
.github/README_ko.md vendored
View File

@ -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)

17
.github/README_nl.md vendored
View File

@ -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)

17
.github/README_pt.md vendored
View File

@ -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)

17
.github/README_ru.md vendored
View File

@ -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)

17
.github/README_sa.md vendored
View File

@ -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)

17
.github/README_tr.md vendored
View File

@ -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)

View File

@ -79,7 +79,9 @@
</a>
</p>
<p align="center">
<b>Fiber</b>是一个受到<a href="https://github.com/expressjs/express">Express</a>启发的<b>Web框架</b>,基于使用<a href="https://go.dev/doc/">Go</a>语言编写的<b>最快的HTTP引擎</b><a href="https://github.com/valyala/fasthttp">Fasthttp</a>构建。旨在通过<b>零内存分配</b><b>高性能服务</b>,使<b>快速</b>开发更加简便。
<b>Fiber</b>是一个受到<a href="https://github.com/expressjs/express"> Express </a>启发的<b>Web框架</b>,基于使用
<a href="https://go.dev/doc/"> Go </a>语言编写的<b>最快的 HTTP 引擎</b>
<a href="https://github.com/valyala/fasthttp"> Fasthttp </a>构建。旨在通过<b>零内存分配</b><b>高性能服务</b>,使<b>快速</b>开发更加简便。
</p>
## ⚡️ 快速入门
@ -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) 。
<p float="left" align="middle">
<img src="https://raw.githubusercontent.com/gofiber/docs/master/.gitbook/assets/benchmark-pipeline.png" width="49%">
@ -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 接口不兼容。也就是说你无法使用 gqlengo-swagger 或者任何其他属于 net/http 生态的项目。
* Fiber 与 net/http 接口不兼容。也就是说你无法直接使用例如 gqlengo-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
<img src="https://opencollective.com/fiber/contributors.svg?width=890&button=false" alt="Code Contributors" style="max-width:100%;">
## ⭐️ 星星数增长情况
## ⭐️ Star 数增长情况
<img src="https://starchart.cc/gofiber/fiber.svg" alt="Stargazers over time" style="max-width: 100%">
@ -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)

View File

@ -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)

View File

@ -1,4 +1,9 @@
on: [push, pull_request]
on:
push:
branches:
- master
- main
pull_request:
name: Linter
jobs:
Golint:

View File

@ -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/*/ ./...
args: -exclude-dir=internal/*/ ./...

View File

@ -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 }}

26
.github/workflows/vulncheck.yml vendored Normal file
View File

@ -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 ./..."

76
app.go
View File

@ -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
}
}

View File

@ -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)
}
}

36
ctx.go
View File

@ -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.

View File

@ -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()

6
go.mod
View File

@ -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
)

8
go.sum
View File

@ -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=

View File

@ -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.

View File

@ -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.

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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",

View File

@ -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
}

21
middleware/logger/data.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
}

211
middleware/logger/tags.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
```
```

View File

@ -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{

View File

@ -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)

View File

@ -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("<title>/debug/pprof/</title>")))
}
func Test_Pprof_Index_WithPrefix(t *testing.T) {
app := fiber.New()
app.Use(New(Config{Prefix: "/federated-fiber"}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("escaped")
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil))
require.Equal(t, nil, err)
require.Equal(t, 200, resp.StatusCode)
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
b, err := io.ReadAll(resp.Body)
require.Equal(t, nil, err)
require.Equal(t, true, bytes.Contains(b, []byte("<title>/debug/pprof/</title>")))
}
func Test_Pprof_Subs(t *testing.T) {
app := fiber.New()
@ -74,6 +111,33 @@ func Test_Pprof_Subs(t *testing.T) {
}
}
func Test_Pprof_Subs_WithPrefix(t *testing.T) {
app := fiber.New()
app.Use(New(Config{Prefix: "/federated-fiber"}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("escaped")
})
subs := []string{
"cmdline", "profile", "symbol", "trace", "allocs", "block",
"goroutine", "heap", "mutex", "threadcreate",
}
for _, sub := range subs {
t.Run(sub, func(t *testing.T) {
target := "/federated-fiber/debug/pprof/" + sub
if sub == "profile" {
target += "?seconds=1"
}
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, target, nil), 5000)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
})
}
}
func Test_Pprof_Other(t *testing.T) {
app := fiber.New()
@ -88,6 +152,20 @@ func Test_Pprof_Other(t *testing.T) {
require.Equal(t, 302, resp.StatusCode)
}
func Test_Pprof_Other_WithPrefix(t *testing.T) {
app := fiber.New()
app.Use(New(Config{Prefix: "/federated-fiber"}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("escaped")
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/302", nil))
require.Equal(t, nil, err)
require.Equal(t, 302, resp.StatusCode)
}
// go test -run Test_Pprof_Next
func Test_Pprof_Next(t *testing.T) {
t.Parallel()
@ -104,3 +182,21 @@ func Test_Pprof_Next(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 404, resp.StatusCode)
}
// go test -run Test_Pprof_Next_WithPrefix
func Test_Pprof_Next_WithPrefix(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New(Config{
Next: func(_ fiber.Ctx) bool {
return true
},
Prefix: "/federated-fiber",
}))
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil))
require.Equal(t, nil, err)
require.Equal(t, 404, resp.StatusCode)
}

View File

@ -22,8 +22,6 @@ type Config struct {
StackTraceHandler func(c fiber.Ctx, e any)
}
var defaultStackTraceBufLen = 1024
// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,

View File

@ -3,15 +3,13 @@ package recover
import (
"fmt"
"os"
"runtime"
"runtime/debug"
"github.com/gofiber/fiber/v3"
)
func defaultStackTraceHandler(c fiber.Ctx, e any) {
buf := make([]byte, defaultStackTraceBufLen)
buf = buf[:runtime.Stack(buf, false)]
_, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, buf))
func defaultStackTraceHandler(_ fiber.Ctx, e interface{}) {
_, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n%s\n", e, debug.Stack()))
}
// New creates a new middleware handler

View File

@ -44,8 +44,10 @@ func (app *App) mount(prefix string, fiber *App) Router {
// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subApp := range fiber.mountFields.appList {
subApp.mountFields.mountPath = prefix + mountedPrefixes
app.mountFields.appList[prefix+mountedPrefixes] = subApp
path := getGroupPath(prefix, mountedPrefixes)
subApp.mountFields.mountPath = path
app.mountFields.appList[path] = subApp
}
// Execute onMount hooks
@ -70,8 +72,10 @@ func (grp *Group) mount(prefix string, fiber *App) Router {
// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subApp := range fiber.mountFields.appList {
subApp.mountFields.mountPath = groupPath + mountedPrefixes
grp.app.mountFields.appList[groupPath+mountedPrefixes] = subApp
path := getGroupPath(groupPath, mountedPrefixes)
subApp.mountFields.mountPath = path
grp.app.mountFields.appList[path] = subApp
}
// Execute onMount hooks
@ -107,7 +111,7 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) {
}
if len(parent) > 0 {
prefix = parent[0] + prefix
prefix = getGroupPath(parent[0], prefix)
}
if _, ok := app.mountFields.appList[prefix]; !ok {
@ -131,7 +135,7 @@ func (app *App) addSubAppsRoutes(appList map[string]*App, parent ...string) {
}
if len(parent) > 0 {
prefix = parent[0] + prefix
prefix = getGroupPath(parent[0], prefix)
}
// add routes

View File

@ -28,6 +28,25 @@ func Test_App_Mount(t *testing.T) {
require.Equal(t, uint32(1), app.handlersCount)
}
func Test_App_Mount_RootPath_Nested(t *testing.T) {
app := New()
dynamic := New()
apiserver := New()
apiroutes := apiserver.Group("/v1")
apiroutes.Get("/home", func(c Ctx) error {
return c.SendString("home")
})
dynamic.Use("/api", apiserver)
app.Use("/", dynamic)
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/v1/home", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, 200, resp.StatusCode, "Status code")
require.Equal(t, uint32(2), app.handlersCount)
}
// go test -run Test_App_Mount_Nested
func Test_App_Mount_Nested(t *testing.T) {
app := New()
@ -138,6 +157,24 @@ func Test_App_Group_Mount(t *testing.T) {
require.Equal(t, uint32(1), app.handlersCount)
}
func Test_App_UseParentErrorHandler(t *testing.T) {
app := New(Config{
ErrorHandler: func(ctx Ctx, err error) error {
return ctx.Status(500).SendString("hi, i'm a custom error")
},
})
fiber := New()
fiber.Get("/", func(c Ctx) error {
return errors.New("something happened")
})
app.Use("/api", fiber)
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil))
testErrorResponse(t, err, resp, "hi, i'm a custom error")
}
func Test_App_UseMountedErrorHandler(t *testing.T) {
app := New()

10
path.go
View File

@ -437,10 +437,12 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma
// take over the params positions
params[paramsIterator] = path[:i]
// check constraint
for _, c := range segment.Constraints {
if matched := c.CheckConstraint(params[paramsIterator]); !matched {
return false
if !(segment.IsOptional && i == 0) {
// check constraint
for _, c := range segment.Constraints {
if matched := c.CheckConstraint(params[paramsIterator]); !matched {
return false
}
}
}

View File

@ -598,6 +598,12 @@ func Test_Path_matchParams(t *testing.T) {
{url: "/api/v1/2005-1101/paach", params: nil, match: false},
{url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true},
})
testCase("/api/v1/:param<int>?", []testparams{
{url: "/api/v1/entity", params: nil, match: false},
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
{url: "/api/v1/true", params: nil, match: false},
{url: "/api/v1/", params: []string{""}, match: true},
})
}
func Test_Utils_GetTrimmedParam(t *testing.T) {
@ -852,4 +858,10 @@ func Benchmark_Path_matchParams(t *testing.B) {
{url: "/api/v1/2005-1101/paach", params: nil, match: false},
{url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true},
})
benchCase("/api/v1/:param<int>?", []testparams{
{url: "/api/v1/entity", params: nil, match: false},
{url: "/api/v1/8728382", params: []string{"8728382"}, match: true},
{url: "/api/v1/true", params: nil, match: false},
{url: "/api/v1/", params: []string{""}, match: true},
})
}

View File

@ -48,14 +48,14 @@ type Registering struct {
//
// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc...
func (r *Registering) All(handler Handler, middleware ...Handler) Register {
r.app.register([]string{methodUse}, r.path, handler, middleware...)
r.app.register([]string{methodUse}, r.path, nil, handler, middleware...)
return r
}
// Get registers a route for GET methods that requests a representation
// of the specified resource. Requests using GET should only retrieve data.
func (r *Registering) Get(handler Handler, middleware ...Handler) Register {
r.app.Add([]string{MethodHead}, r.path, handler, middleware...).Add([]string{MethodGet}, r.path, handler, middleware...)
r.app.Add([]string{MethodGet}, r.path, handler, middleware...)
return r
}
@ -108,7 +108,7 @@ func (r *Registering) Patch(handler Handler, middleware ...Handler) Register {
// Add allows you to specify multiple HTTP methods to register a route.
func (r *Registering) Add(methods []string, handler Handler, middleware ...Handler) Register {
r.app.register(methods, r.path, handler, middleware...)
r.app.register(methods, r.path, nil, handler, middleware...)
return r
}

View File

@ -50,6 +50,7 @@ type Route struct {
root bool // Path equals '/'
path string // Prettified path
routeParser routeParser // Parameter parser
group *Group // Group instance. used for routes in groups
// Public fields
Method string `json:"method"` // HTTP method
@ -136,7 +137,7 @@ func (app *App) nextCustom(c CustomCtx) (match bool, err error) {
// If no match, scan stack again if other methods match the request
// Moved from app.handler because middleware may break the route chain
if !c.getMatched() && methodExistCustom(c) {
if !c.getMatched() && app.methodExistCustom(c) {
err = ErrMethodNotAllowed
}
return
@ -184,7 +185,7 @@ func (app *App) next(c *DefaultCtx) (match bool, err error) {
// If no match, scan stack again if other methods match the request
// Moved from app.handler because middleware may break the route chain
if !c.matched && methodExist(c) {
if !c.matched && app.methodExist(c) {
err = ErrMethodNotAllowed
}
return
@ -202,7 +203,7 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) {
defer app.ReleaseCtx(c)
// handle invalid http method directly
if methodInt(c.Method()) == -1 {
if app.methodInt(c.Method()) == -1 {
_ = c.SendStatus(StatusNotImplemented)
return
}
@ -266,7 +267,7 @@ func (app *App) copyRoute(route *Route) *Route {
}
}
func (app *App) register(methods []string, pathRaw string, handler Handler, middleware ...Handler) Router {
func (app *App) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) Router {
handlers := middleware
if handler != nil {
handlers = append(handlers, handler)
@ -277,7 +278,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
// Uppercase HTTP methods
method = utils.ToUpper(method)
// Check if the HTTP method is valid unless it's USE
if method != methodUse && methodInt(method) == -1 {
if method != methodUse && app.methodInt(method) == -1 {
panic(fmt.Sprintf("add: invalid http method %s\n", method))
}
// A route requires atleast one ctx handler
@ -324,6 +325,9 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
routeParser: parsedPretty,
Params: parsedRaw.params,
// Group data
group: group,
// Public data
Path: pathRaw,
Method: method,
@ -335,7 +339,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
// Middleware route matches all HTTP methods
if isUse {
// Add route to all HTTP methods stack
for _, m := range intMethod {
for _, m := range app.config.RequestMethods {
// Create a route copy to avoid duplicates during compression
r := route
app.addRoute(m, &r)
@ -496,7 +500,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
}
// Get unique HTTP method identifier
m := methodInt(method)
m := app.methodInt(method)
// prevent identically route registration
l := len(app.stack[m])
@ -530,7 +534,7 @@ func (app *App) buildTree() *App {
}
// loop all the methods and stacks and create the prefix tree
for m := range intMethod {
for m := range app.config.RequestMethods {
tsMap := make(map[string][]*Route)
for _, route := range app.stack[m] {
treePath := ""
@ -544,7 +548,7 @@ func (app *App) buildTree() *App {
}
// loop the methods and tree stacks and add global stack and sort everything
for m := range intMethod {
for m := range app.config.RequestMethods {
tsMap := app.treeStack[m]
for treePart := range tsMap {
if treePart != "" {

View File

@ -281,7 +281,7 @@ func Test_Router_Register_Missing_Handler(t *testing.T) {
require.Equal(t, "missing handler/middleware in route: /doe\n", fmt.Sprintf("%v", err))
}
}()
app.register([]string{"USE"}, "/doe", nil)
app.register([]string{"USE"}, "/doe", nil, nil)
}
func Test_Ensure_Router_Interface_Implementation(t *testing.T) {