mirror of https://github.com/gofiber/fiber.git
Merge remote-tracking branch 'origin/master' into v3-beta
commit
c62dd16e8d
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 接口不兼容。也就是说你无法使用 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。
|
||||
* Fiber 与 net/http 接口不兼容。也就是说你无法直接使用例如 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。
|
||||
|
||||
## 👀 示例
|
||||
|
||||
下面列出了一些常见示例。如果您想查看更多代码示例,请访问我们的[Recipes](https://github.com/gofiber/recipes)代码库或[API 文档](https://docs.gofiber.io) 。
|
||||
下面列出了一些常见示例。如果您想查看更多代码示例,请访问我们的 [Recipes](https://github.com/gofiber/recipes) 代码库或 [API 文档](https://docs.gofiber.io) 。
|
||||
|
||||
#### 📖 [**基础路由**](https://docs.gofiber.io/#basic-routing)
|
||||
|
||||
|
@ -244,7 +249,7 @@ func main() {
|
|||
|
||||
```
|
||||
|
||||
#### 📖 [**中间件**](https://docs.gofiber.io/middleware)和[**Next**](https://docs.gofiber.io/api/ctx#next)
|
||||
#### 📖 [**中间件**](https://docs.gofiber.io/middleware)和 [**Next**](https://docs.gofiber.io/api/ctx#next)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
|
@ -282,11 +287,12 @@ func main() {
|
|||
📖 [模版引擎](https://github.com/gofiber/template)
|
||||
📖 [渲染](https://docs.gofiber.io/api/ctx#render)
|
||||
|
||||
如果未设置模版引擎,则`Fiber`默认使用[html/template](https://pkg.go.dev/html/template/)。
|
||||
如果未设置模版引擎,则`Fiber`默认使用 [html/template](https://pkg.go.dev/html/template/)。
|
||||
|
||||
如果您要执行部分模版或使用其他引擎,例如[amber](https://github.com/eknkc/amber),[handlebars](https://github.com/aymerick/raymond),[mustache](https://github.com/cbroglie/mustache)或者[pug](https://github.com/Joker/jade)等等...
|
||||
如果您要执行部分模版或使用其他引擎,例如[amber](https://github.com/eknkc/amber),[handlebars](https://github.com/aymerick/raymond),
|
||||
[mustache](https://github.com/cbroglie/mustache) 或者 [pug](https://github.com/Joker/jade)等
|
||||
|
||||
请查看我们的[Template](https://github.com/gofiber/template)包,该包支持多个模版引擎。
|
||||
请查看我们的 [Template](https://github.com/gofiber/template) 包,该包支持多个模版引擎。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
@ -397,7 +403,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
通过在请求头中设置`Origin`传递任何域来检查 CORS:
|
||||
通过在请求头中设置 `Origin` 传递任何域来检查 CORS :
|
||||
|
||||
```bash
|
||||
curl -H "Origin: http://example.com" --verbose http://localhost:3000
|
||||
|
@ -461,7 +467,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
### 升级到 WebSocket
|
||||
### 使用 WebSocket 中间件
|
||||
|
||||
📖 [Websocket](https://github.com/gofiber/websocket)
|
||||
|
||||
|
@ -565,45 +571,45 @@ func main() {
|
|||
|
||||
以下为包含在Fiber框架中的中间件列表.
|
||||
|
||||
| 中间件 | 描述 |
|
||||
| :------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | 基本身份验证中间件提供HTTP基本身份验证。 它为有效凭证调用下一个处理程序,为丢失或无效凭证调用401 Unauthorized. |
|
||||
| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | 拦截和缓存响应. |
|
||||
| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Fiber的压缩中间件,默认支持' deflate ', ' gzip '和' brotli '. |
|
||||
| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | 使用各种选项启用跨源资源共享\(CORS\). |
|
||||
| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | 保护来自CSRF的漏洞. |
|
||||
| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | 加密 cookie 值的加密中间件. |
|
||||
| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | 通过提供可选配置来公开环境变量。. |
|
||||
| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | 让缓存更加高效并且节省带宽,让web服务不再需要重新响应整个响应体如果响应内容未变更. |
|
||||
| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | 通过其HTTP服务器运行时间提供JSON格式的暴露变体. |
|
||||
| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路径,则忽略日志中的图标或从内存中服务. |
|
||||
| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Fiber文件系统中间件,特别感谢 Alireza Salary. |
|
||||
| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | 用于Fiber的限速中间件。 用于限制对公共api和/或端点的重复请求,如密码重置. |
|
||||
| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP请求/响应日志. |
|
||||
| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | 报告服务器指标,受Express-status-monitor启发. |
|
||||
| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | 特别感谢 Matthew Lee \(@mthli\) |
|
||||
| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | 允许您将请求proxy到多个服务器 |
|
||||
| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | 恢复中间件从堆栈链中的任何位置的恐慌中恢复,并将控制处理到集中式. [ ErrorHandler](https://docs.gofiber.io/guide/error-handling). |
|
||||
| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | 为每个请求添加一个requesttid. |
|
||||
| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session 中间件. 注意: 此中间件使用了我们的存储包. |
|
||||
| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip middleware that skips a wrapped handler is a predicate is true. |
|
||||
| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | 添加请求的最大时间,如果超过了,则发送给ErrorHandler. |
|
||||
| 中间件 | 描述 |
|
||||
|:---------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------|
|
||||
| [basicauth](https://github.com/gofiber/fiber/tree/master/middleware/basicauth) | 基本身份验证中间件提供 HTTP 基本身份验证。 它为有效凭证调用下一个处理程序,为丢失或无效凭证调用 401 Unauthorized |
|
||||
| [cache](https://github.com/gofiber/fiber/tree/master/middleware/cache) | 用于拦截和缓存响应 |
|
||||
| [compress](https://github.com/gofiber/fiber/tree/master/middleware/compress) | Fiber 的压缩中间件,默认支持 `deflate`,`gzip` 和 `brotli` |
|
||||
| [cors](https://github.com/gofiber/fiber/tree/master/middleware/cors) | 使用各种选项启用跨源资源共享\(CORS\) |
|
||||
| [csrf](https://github.com/gofiber/fiber/tree/master/middleware/csrf) | 保护来自 CSRF 的漏洞 |
|
||||
| [encryptcookie](https://github.com/gofiber/fiber/tree/master/middleware/encryptcookie) | 加密 cookie 值的加密中间件 |
|
||||
| [envvar](https://github.com/gofiber/fiber/tree/master/middleware/envvar) | 通过提供可选配置来公开环境变量 |
|
||||
| [etag](https://github.com/gofiber/fiber/tree/master/middleware/etag) | 让缓存更加高效并且节省带宽, 让 web 服务在响应内容未变更的情况下不再需要重发送整个响应体 |
|
||||
| [expvar](https://github.com/gofiber/fiber/tree/master/middleware/expvar) | 通过其 HTTP 服务器运行时间提供 JSON 格式的暴露变体 |
|
||||
| [favicon](https://github.com/gofiber/fiber/tree/master/middleware/favicon) | 如果提供了文件路径,则忽略日志中的图标或从内存中服务 |
|
||||
| [filesystem](https://github.com/gofiber/fiber/tree/master/middleware/filesystem) | Fiber 文件系统中间件,特别感谢 Alireza Salary |
|
||||
| [limiter](https://github.com/gofiber/fiber/tree/master/middleware/limiter) | 用于 Fiber 的限速中间件。 用于限制对公共 api 或对端点的重复请求,如密码重置 |
|
||||
| [logger](https://github.com/gofiber/fiber/tree/master/middleware/logger) | HTTP 请求/响应日志 |
|
||||
| [monitor](https://github.com/gofiber/fiber/tree/master/middleware/monitor) | 用于报告服务器指标,受 Express-status-monitor 启发 |
|
||||
| [pprof](https://github.com/gofiber/fiber/tree/master/middleware/pprof) | 特别感谢 Matthew Lee \(@mthli\) |
|
||||
| [proxy](https://github.com/gofiber/fiber/tree/master/middleware/proxy) | 允许您将请求proxy到多个服务器 |
|
||||
| [recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) | Recover 中间件将可以堆栈链中的任何位置将 panic 恢复,并将处理集中到 [ErrorHandler](https://docs.gofiber.io/guide/error-handling) |
|
||||
| [requestid](https://github.com/gofiber/fiber/tree/master/middleware/requestid) | 为每个请求添加一个 requestid. |
|
||||
| [session](https://github.com/gofiber/fiber/tree/master/middleware/session) | Session 中间件. 注意: 此中间件使用了我们的存储包. |
|
||||
| [skip](https://github.com/gofiber/fiber/tree/master/middleware/skip) | Skip 中间件会在判断条为 true 时忽略此次请求 |
|
||||
| [timeout](https://github.com/gofiber/fiber/tree/master/middleware/timeout) | 添加请求的最大时间,如果超时则发送给ErrorHandler 进行处理. |
|
||||
|
||||
## 🧬 外部中间件
|
||||
|
||||
以下为外部托管的中间件列表,由[Fiber团队](https://github.com/orgs/gofiber/people)维护。
|
||||
以下为外部托管的中间件列表,由 [Fiber团队](https://github.com/orgs/gofiber/people) 维护。
|
||||
|
||||
| 中间件 | 描述 |
|
||||
| :------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [adaptor](https://github.com/gofiber/adaptor) | net/http处理程序与Fiber请求处理程序之间的转换器,特别感谢 @arsmn! |
|
||||
| [helmet](https://github.com/gofiber/helmet) | 通过设置各种HTTP头帮助保护您的应用程序。 |
|
||||
| [jwt](https://github.com/gofiber/jwt) | JWT返回一个JSON Web Token\(JWT\)身份验证中间件。 |
|
||||
| [keyauth](https://github.com/gofiber/keyauth) | Key auth中间件提供基于密钥的身份验证。 |
|
||||
| [redirect](https://github.com/gofiber/redirect) | Redirect middleware |
|
||||
| [rewrite](https://github.com/gofiber/rewrite) | Rewrite中间件根据提供的规则重写URL路径。它有助于向后兼容或者创建更清晰、更具描述性的链接。 |
|
||||
| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. |
|
||||
| [template](https://github.com/gofiber/template) | 该软件包包含8个模板引擎,可与Fiber `v1.10.x` Go 1.13或更高版本一起使用。 |
|
||||
| [websocket](https://github.com/gofiber/websocket) | 基于 Fasthttp WebSocket for Fiber与Locals支持! |
|
||||
| 中间件 | 描述 |
|
||||
|:--------------------------------------------------|:-------------------------------------------------------------------------------------------|
|
||||
| [adaptor](https://github.com/gofiber/adaptor) | net/http 处理程序与 Fiber 请求处理程序之间的转换器,特别感谢 @arsmn! |
|
||||
| [helmet](https://github.com/gofiber/helmet) | 通过设置各种 HTTP 头帮助保护您的应用程序 |
|
||||
| [jwt](https://github.com/gofiber/jwt) | JWT 返回一个 JSON Web Token\(JWT\) 身份验证中间件 |
|
||||
| [keyauth](https://github.com/gofiber/keyauth) | Key auth 中间件提供基于密钥的身份验证 |
|
||||
| [redirect](https://github.com/gofiber/redirect) | 用于重定向请求的中间件 |
|
||||
| [rewrite](https://github.com/gofiber/rewrite) | Rewrite 中间件根据提供的规则重写URL路径。它有助于向后兼容或者创建更清晰、更具描述性的链接 |
|
||||
| [storage](https://github.com/gofiber/storage) | 包含实现 Storage 接口的数据库驱动,它的设计旨在配合 fiber 的其他中间件来进行使用 |
|
||||
| [template](https://github.com/gofiber/template) | 该中间件包含 8 个模板引擎,可与 Fiber `v1.10.x` Go 1.13或更高版本一起使用 |
|
||||
| [websocket](https://github.com/gofiber/websocket) | 基于 Fasthttp WebSocket for Fiber 实现,支持使用 [Locals](https://docs.gofiber.io/api/ctx#locals) ! |
|
||||
|
||||
## 🕶️ Awesome List
|
||||
|
||||
|
@ -611,16 +617,17 @@ For more articles, middlewares, examples or tools check our [awesome list](https
|
|||
|
||||
## 👍 贡献
|
||||
|
||||
如果想**感谢**我们或支持`Fiber`的积极发展:
|
||||
如果想**感谢**我们或支持 `Fiber` 的积极发展:
|
||||
|
||||
1. 为`Fiber`[GitHub Star](https://github.com/gofiber/fiber/stargazers)点个 ⭐ 星星。
|
||||
2. 在[Twitter](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber)上发布有关项目的[推文](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber)。
|
||||
3. 在[Medium](https://medium.com/),[Dev.to](https://dev.to/)或个人博客上写评论或教程。
|
||||
1. 为 [`Fiber`](https://github.com/gofiber/fiber/stargazers) 点个 ⭐ 星星。
|
||||
2. 在 [Twitter](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber) 上发布有关项目的[推文](https://twitter.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber)。
|
||||
3. 在 [Medium](https://medium.com/),[Dev.to](https://dev.to/) 或个人博客上写评论或教程。
|
||||
4. 通过捐赠[一杯咖啡](https://buymeacoff.ee/fenny)来支持本项目。
|
||||
|
||||
## ☕ 支持者
|
||||
|
||||
`Fiber`是一个开源项目,依靠捐赠来支付账单,例如我们的域名,`gitbook`,`netlify`和无服务器托管。如果要支持`Fiber`,可以 ☕ [**在这里买一杯咖啡**](https://buymeacoff.ee/fenny)
|
||||
`Fiber`是一个开源项目,依靠捐赠来支付账单,例如我们的域名,`gitbook`,`netlify` 和无服务器托管。如果要支持 `Fiber`,
|
||||
可以 ☕ [**在这里买一杯咖啡**](https://buymeacoff.ee/fenny)
|
||||
|
||||
| | User | Donation |
|
||||
| :--------------------------------------------------------- | :----------------------------------------------- | :------- |
|
||||
|
@ -647,7 +654,7 @@ For more articles, middlewares, examples or tools check our [awesome list](https
|
|||
|
||||
<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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
name: Linter
|
||||
jobs:
|
||||
Golint:
|
||||
|
|
|
@ -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/*/ ./...
|
||||
|
|
|
@ -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 }}
|
|
@ -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
76
app.go
|
@ -108,7 +108,6 @@ type App struct {
|
|||
hooks *Hooks
|
||||
// Latest route & group
|
||||
latestRoute *Route
|
||||
latestGroup *Group
|
||||
// newCtxFunc
|
||||
newCtxFunc func(app *App) CustomCtx
|
||||
// custom binders
|
||||
|
@ -117,6 +116,8 @@ type App struct {
|
|||
tlsHandler *TLSHandler
|
||||
// Mount fields
|
||||
mountFields *mountFields
|
||||
// Indicates if the value was explicitly configured
|
||||
configured Config
|
||||
}
|
||||
|
||||
// Config is a struct holding the server settings.
|
||||
|
@ -365,6 +366,11 @@ type Config struct {
|
|||
//
|
||||
// Default: nil
|
||||
StructValidator StructValidator
|
||||
|
||||
// RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish.
|
||||
//
|
||||
// Optional. Default: DefaultMethods
|
||||
RequestMethods []string
|
||||
}
|
||||
|
||||
// Static defines configuration options when defining static assets.
|
||||
|
@ -430,6 +436,19 @@ const (
|
|||
DefaultCompressedFileSuffix = ".fiber.gz"
|
||||
)
|
||||
|
||||
// HTTP methods enabled by default
|
||||
var DefaultMethods = []string{
|
||||
MethodGet,
|
||||
MethodHead,
|
||||
MethodPost,
|
||||
MethodPut,
|
||||
MethodDelete,
|
||||
MethodConnect,
|
||||
MethodOptions,
|
||||
MethodTrace,
|
||||
MethodPatch,
|
||||
}
|
||||
|
||||
// DefaultErrorHandler that process return errors from handlers
|
||||
var DefaultErrorHandler = func(c Ctx, err error) error {
|
||||
code := StatusInternalServerError
|
||||
|
@ -454,15 +473,11 @@ var DefaultErrorHandler = func(c Ctx, err error) error {
|
|||
func New(config ...Config) *App {
|
||||
// Create a new app
|
||||
app := &App{
|
||||
// Create router stack
|
||||
stack: make([][]*Route, len(intMethod)),
|
||||
treeStack: make([]map[string][]*Route, len(intMethod)),
|
||||
// Create config
|
||||
config: Config{},
|
||||
getBytes: utils.UnsafeBytes,
|
||||
getString: utils.UnsafeString,
|
||||
latestRoute: &Route{},
|
||||
latestGroup: &Group{},
|
||||
customBinders: []CustomBinder{},
|
||||
}
|
||||
|
||||
|
@ -484,6 +499,9 @@ func New(config ...Config) *App {
|
|||
app.config = config[0]
|
||||
}
|
||||
|
||||
// Initialize configured before defaults are set
|
||||
app.configured = app.config
|
||||
|
||||
// Override default values
|
||||
if app.config.BodyLimit == 0 {
|
||||
app.config.BodyLimit = DefaultBodyLimit
|
||||
|
@ -517,12 +535,19 @@ func New(config ...Config) *App {
|
|||
if app.config.XMLEncoder == nil {
|
||||
app.config.XMLEncoder = xml.Marshal
|
||||
}
|
||||
if len(app.config.RequestMethods) == 0 {
|
||||
app.config.RequestMethods = DefaultMethods
|
||||
}
|
||||
|
||||
app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies))
|
||||
for _, ipAddress := range app.config.TrustedProxies {
|
||||
app.handleTrustedProxy(ipAddress)
|
||||
}
|
||||
|
||||
// Create router stack
|
||||
app.stack = make([][]*Route, len(app.config.RequestMethods))
|
||||
app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods))
|
||||
|
||||
// Override colors
|
||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||
|
||||
|
@ -571,8 +596,10 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) {
|
|||
// Name Assign name to specific route.
|
||||
func (app *App) Name(name string) Router {
|
||||
app.mutex.Lock()
|
||||
if strings.HasPrefix(app.latestRoute.path, app.latestGroup.Prefix) {
|
||||
app.latestRoute.Name = app.latestGroup.name + name
|
||||
|
||||
latestGroup := app.latestRoute.group
|
||||
if latestGroup != nil {
|
||||
app.latestRoute.Name = latestGroup.name + name
|
||||
} else {
|
||||
app.latestRoute.Name = name
|
||||
}
|
||||
|
@ -640,6 +667,7 @@ func (app *App) GetRoutes(filterUseOption ...bool) []Route {
|
|||
func (app *App) Use(args ...any) Router {
|
||||
var prefix string
|
||||
var subApp *App
|
||||
var prefixes []string
|
||||
var handlers []Handler
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
|
@ -648,6 +676,8 @@ func (app *App) Use(args ...any) Router {
|
|||
prefix = arg
|
||||
case *App:
|
||||
subApp = arg
|
||||
case []string:
|
||||
prefixes = arg
|
||||
case Handler:
|
||||
handlers = append(handlers, arg)
|
||||
default:
|
||||
|
@ -655,13 +685,19 @@ func (app *App) Use(args ...any) Router {
|
|||
}
|
||||
}
|
||||
|
||||
if subApp != nil {
|
||||
app.mount(prefix, subApp)
|
||||
return app
|
||||
if len(prefixes) == 0 {
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
for _, prefix := range prefixes {
|
||||
if subApp != nil {
|
||||
app.mount(prefix, subApp)
|
||||
return app
|
||||
}
|
||||
|
||||
app.register([]string{methodUse}, prefix, nil, nil, handlers...)
|
||||
}
|
||||
|
||||
app.register([]string{methodUse}, prefix, nil, handlers...)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
@ -720,7 +756,7 @@ func (app *App) Patch(path string, handler Handler, middleware ...Handler) Route
|
|||
|
||||
// Add allows you to specify multiple HTTP methods to register a route.
|
||||
func (app *App) Add(methods []string, path string, handler Handler, middleware ...Handler) Router {
|
||||
return app.register(methods, path, handler, middleware...)
|
||||
return app.register(methods, path, nil, handler, middleware...)
|
||||
}
|
||||
|
||||
// Static will create a file server serving static files
|
||||
|
@ -730,8 +766,7 @@ func (app *App) Static(prefix, root string, config ...Static) Router {
|
|||
|
||||
// All will register the handler on all HTTP methods
|
||||
func (app *App) All(path string, handler Handler, middleware ...Handler) Router {
|
||||
app.Add(intMethod, path, handler, middleware...)
|
||||
return app
|
||||
return app.Add(app.config.RequestMethods, path, handler, middleware...)
|
||||
}
|
||||
|
||||
// Group is used for Routes with common prefix to define a new sub-router with optional middleware.
|
||||
|
@ -739,10 +774,10 @@ func (app *App) All(path string, handler Handler, middleware ...Handler) Router
|
|||
// api := app.Group("/api")
|
||||
// api.Get("/users", handler)
|
||||
func (app *App) Group(prefix string, handlers ...Handler) Router {
|
||||
if len(handlers) > 0 {
|
||||
app.register([]string{methodUse}, prefix, nil, handlers...)
|
||||
}
|
||||
grp := &Group{Prefix: prefix, app: app}
|
||||
if len(handlers) > 0 {
|
||||
app.register([]string{methodUse}, prefix, grp, nil, handlers...)
|
||||
}
|
||||
if err := app.hooks.executeOnGroupHooks(*grp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -961,7 +996,10 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error {
|
|||
if prefix != "" && strings.HasPrefix(ctx.Path(), prefix) {
|
||||
parts := len(strings.Split(prefix, "/"))
|
||||
if mountedPrefixParts <= parts {
|
||||
mountedErrHandler = subApp.config.ErrorHandler
|
||||
if subApp.configured.ErrorHandler != nil {
|
||||
mountedErrHandler = subApp.config.ErrorHandler
|
||||
}
|
||||
|
||||
mountedPrefixParts = parts
|
||||
}
|
||||
}
|
||||
|
|
113
app_test.go
113
app_test.go
|
@ -398,6 +398,52 @@ func Test_App_Not_Use_StrictRouting(t *testing.T) {
|
|||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
func Test_App_Use_MultiplePrefix(t *testing.T) {
|
||||
app := New()
|
||||
|
||||
app.Use([]string{"/john", "/doe"}, func(c Ctx) error {
|
||||
return c.SendString(c.Path())
|
||||
})
|
||||
|
||||
g := app.Group("/test")
|
||||
g.Use([]string{"/john", "/doe"}, func(c Ctx) error {
|
||||
return c.SendString(c.Path())
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/john", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "/john", string(body))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "/doe", string(body))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/john", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "/test/john", string(body))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test/doe", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "/test/doe", string(body))
|
||||
|
||||
}
|
||||
|
||||
func Test_App_Use_StrictRouting(t *testing.T) {
|
||||
app := New(Config{StrictRouting: true})
|
||||
|
||||
|
@ -432,13 +478,32 @@ func Test_App_Use_StrictRouting(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_App_Add_Method_Test(t *testing.T) {
|
||||
app := New()
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
require.Equal(t, "add: invalid http method JOHN\n", fmt.Sprintf("%v", err))
|
||||
require.Equal(t, "add: invalid http method JANE\n", fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
methods := append(DefaultMethods, "JOHN")
|
||||
app := New(Config{
|
||||
RequestMethods: methods,
|
||||
})
|
||||
|
||||
app.Add([]string{"JOHN"}, "/doe", testEmptyHandler)
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/doe", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusBadRequest, resp.StatusCode, "Status code")
|
||||
|
||||
app.Add([]string{"JANE"}, "/doe", testEmptyHandler)
|
||||
}
|
||||
|
||||
// go test -run Test_App_GETOnly
|
||||
|
@ -484,7 +549,7 @@ func Test_App_Chaining(t *testing.T) {
|
|||
return c.SendStatus(202)
|
||||
})
|
||||
// check handler count for registered HEAD route
|
||||
require.Equal(t, 5, len(app.stack[methodInt(MethodHead)][0].Handlers), "app.Test(req)")
|
||||
require.Equal(t, 5, len(app.stack[app.methodInt(MethodHead)][0].Handlers), "app.Test(req)")
|
||||
|
||||
req := httptest.NewRequest(MethodPost, "/john", nil)
|
||||
|
||||
|
@ -581,15 +646,16 @@ func Test_App_Route_Naming(t *testing.T) {
|
|||
app.Name("doe")
|
||||
|
||||
jane := app.Group("/jane").Name("jane.")
|
||||
group := app.Group("/group")
|
||||
subGroup := jane.Group("/sub-group").Name("sub.")
|
||||
|
||||
jane.Get("/test", handler).Name("test")
|
||||
jane.Trace("/trace", handler).Name("trace")
|
||||
|
||||
group := app.Group("/group")
|
||||
group.Get("/test", handler).Name("test")
|
||||
|
||||
app.Post("/post", handler).Name("post")
|
||||
|
||||
subGroup := jane.Group("/sub-group").Name("sub.")
|
||||
subGroup.Get("/done", handler).Name("done")
|
||||
|
||||
require.Equal(t, "post", app.GetRoute("post").Name)
|
||||
|
@ -1218,16 +1284,17 @@ func Test_App_Stack(t *testing.T) {
|
|||
app.Post("/path3", testEmptyHandler)
|
||||
|
||||
stack := app.Stack()
|
||||
require.Equal(t, 9, len(stack))
|
||||
require.Equal(t, 3, len(stack[methodInt(MethodGet)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodHead)]))
|
||||
require.Equal(t, 2, len(stack[methodInt(MethodPost)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodPut)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodPatch)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodDelete)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodConnect)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodOptions)]))
|
||||
require.Equal(t, 1, len(stack[methodInt(MethodTrace)]))
|
||||
methodList := app.config.RequestMethods
|
||||
require.Equal(t, len(methodList), len(stack))
|
||||
require.Equal(t, 3, len(stack[app.methodInt(MethodGet)]))
|
||||
require.Equal(t, 3, len(stack[app.methodInt(MethodHead)]))
|
||||
require.Equal(t, 2, len(stack[app.methodInt(MethodPost)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodPut)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodPatch)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodDelete)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodConnect)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodOptions)]))
|
||||
require.Equal(t, 1, len(stack[app.methodInt(MethodTrace)]))
|
||||
}
|
||||
|
||||
// go test -run Test_App_HandlersCount
|
||||
|
@ -1477,6 +1544,19 @@ func Test_App_SetTLSHandler(t *testing.T) {
|
|||
require.Equal(t, "example.golang", c.ClientHelloInfo().ServerName)
|
||||
}
|
||||
|
||||
func Test_App_AddCustomRequestMethod(t *testing.T) {
|
||||
methods := append(DefaultMethods, "TEST")
|
||||
app := New(Config{
|
||||
RequestMethods: methods,
|
||||
})
|
||||
appMethods := app.config.RequestMethods
|
||||
|
||||
// method name is always uppercase - https://datatracker.ietf.org/doc/html/rfc7231#section-4.1
|
||||
require.Equal(t, len(app.stack), len(appMethods))
|
||||
require.Equal(t, len(app.stack), len(appMethods))
|
||||
require.Equal(t, "TEST", appMethods[len(appMethods)-1])
|
||||
}
|
||||
|
||||
func TestApp_GetRoutes(t *testing.T) {
|
||||
app := New()
|
||||
app.Use(func(c Ctx) error {
|
||||
|
@ -1488,7 +1568,7 @@ func TestApp_GetRoutes(t *testing.T) {
|
|||
app.Delete("/delete", handler).Name("delete")
|
||||
app.Post("/post", handler).Name("post")
|
||||
routes := app.GetRoutes(false)
|
||||
require.Equal(t, 11, len(routes))
|
||||
require.Equal(t, 2+len(app.config.RequestMethods), len(routes))
|
||||
methodMap := map[string]string{"/delete": "delete", "/post": "post"}
|
||||
for _, route := range routes {
|
||||
name, ok := methodMap[route.Path]
|
||||
|
@ -1504,5 +1584,4 @@ func TestApp_GetRoutes(t *testing.T) {
|
|||
require.Equal(t, true, ok)
|
||||
require.Equal(t, name, route.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
36
ctx.go
36
ctx.go
|
@ -732,7 +732,7 @@ func (c *DefaultCtx) Location(path string) {
|
|||
func (c *DefaultCtx) Method(override ...string) string {
|
||||
if len(override) > 0 {
|
||||
method := utils.ToUpper(override[0])
|
||||
mINT := methodInt(method)
|
||||
mINT := c.app.methodInt(method)
|
||||
if mINT == -1 {
|
||||
return c.method
|
||||
}
|
||||
|
@ -855,29 +855,26 @@ func (c *DefaultCtx) Path(override ...string) string {
|
|||
return c.path
|
||||
}
|
||||
|
||||
// Scheme contains the request scheme string: http or https for TLS requests.
|
||||
// Use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
|
||||
// Scheme contains the request protocol string: http or https for TLS requests.
|
||||
// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
|
||||
func (c *DefaultCtx) Scheme() string {
|
||||
if c.fasthttp.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
scheme := "http"
|
||||
if !c.IsProxyTrusted() {
|
||||
return scheme
|
||||
return "http"
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
|
||||
if len(key) < 12 {
|
||||
return // X-Forwarded-
|
||||
} else if bytes.HasPrefix(key, []byte("X-Forwarded-")) {
|
||||
v := c.app.getString(val)
|
||||
if bytes.Equal(key, []byte(HeaderXForwardedProto)) {
|
||||
commaPos := strings.Index(v, ",")
|
||||
if commaPos != -1 {
|
||||
scheme = v[:commaPos]
|
||||
} else {
|
||||
scheme = v
|
||||
}
|
||||
} else if bytes.Equal(key, []byte(HeaderXForwardedProtocol)) {
|
||||
return // Neither "X-Forwarded-" nor "X-Url-Scheme"
|
||||
}
|
||||
switch {
|
||||
case bytes.HasPrefix(key, []byte("X-Forwarded-")):
|
||||
if bytes.Equal(key, []byte(HeaderXForwardedProto)) ||
|
||||
bytes.Equal(key, []byte(HeaderXForwardedProtocol)) {
|
||||
v := c.app.getString(val)
|
||||
commaPos := strings.Index(v, ",")
|
||||
if commaPos != -1 {
|
||||
scheme = v[:commaPos]
|
||||
|
@ -887,7 +884,8 @@ func (c *DefaultCtx) Scheme() string {
|
|||
} else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) {
|
||||
scheme = "https"
|
||||
}
|
||||
} else if bytes.Equal(key, []byte(HeaderXUrlScheme)) {
|
||||
|
||||
case bytes.Equal(key, []byte(HeaderXUrlScheme)):
|
||||
scheme = c.app.getString(val)
|
||||
}
|
||||
})
|
||||
|
@ -1135,9 +1133,9 @@ func (c *DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path st
|
|||
return storage.Set(path, content, 0)
|
||||
}
|
||||
|
||||
// Secure returns a boolean property, that is true, if a TLS connection is established.
|
||||
// Secure returns whether a secure connection was established.
|
||||
func (c *DefaultCtx) Secure() bool {
|
||||
return c.fasthttp.IsTLS()
|
||||
return c.Protocol() == "https"
|
||||
}
|
||||
|
||||
// Send sets the HTTP response body without copying it.
|
||||
|
|
|
@ -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
6
go.mod
|
@ -3,6 +3,7 @@ module github.com/gofiber/fiber/v3
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v2 v2.40.1
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
|
@ -10,8 +11,7 @@ require (
|
|||
github.com/stretchr/testify v1.8.1
|
||||
github.com/tinylib/msgp v1.1.6
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/valyala/fasthttp v1.41.0
|
||||
github.com/valyala/fasttemplate v1.2.1
|
||||
github.com/valyala/fasthttp v1.42.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -21,6 +21,6 @@ require (
|
|||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -3,6 +3,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4=
|
||||
github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.1 h1:ACfPdqeclx+BFIja19UjkKx7k3r5tmpILpNgzrfPLKs=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.1/go.mod h1:CG89nDoIkEFIJaw5LdLO9AmBM11odse/LC79KQujm74=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
|
@ -31,8 +33,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY=
|
||||
github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasthttp v1.42.0 h1:LBMyqvJR8DEBgN79oI8dGbkuj5Lm9jbHESxH131TTN8=
|
||||
github.com/valyala/fasthttp v1.42.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -58,6 +60,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
53
group.go
53
group.go
|
@ -7,13 +7,13 @@ package fiber
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Group struct
|
||||
type Group struct {
|
||||
app *App
|
||||
name string
|
||||
app *App
|
||||
parentGroup *Group
|
||||
name string
|
||||
|
||||
Prefix string
|
||||
}
|
||||
|
@ -21,15 +21,14 @@ type Group struct {
|
|||
// Name Assign name to specific route.
|
||||
func (grp *Group) Name(name string) Router {
|
||||
grp.app.mutex.Lock()
|
||||
if strings.HasPrefix(grp.Prefix, grp.app.latestGroup.Prefix) {
|
||||
grp.name = grp.app.latestGroup.name + name
|
||||
|
||||
if grp.parentGroup != nil {
|
||||
grp.name = grp.parentGroup.name + name
|
||||
} else {
|
||||
grp.name = name
|
||||
}
|
||||
|
||||
grp.app.latestGroup = grp
|
||||
|
||||
if err := grp.app.hooks.executeOnGroupNameHooks(*grp.app.latestGroup); err != nil {
|
||||
if err := grp.app.hooks.executeOnGroupNameHooks(*grp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
grp.app.mutex.Unlock()
|
||||
|
@ -59,8 +58,9 @@ func (grp *Group) Name(name string) Router {
|
|||
//
|
||||
// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc...
|
||||
func (grp *Group) Use(args ...any) Router {
|
||||
prefix := ""
|
||||
var subApp *App
|
||||
var prefix string
|
||||
var prefixes []string
|
||||
var handlers []Handler
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
|
@ -69,6 +69,8 @@ func (grp *Group) Use(args ...any) Router {
|
|||
prefix = arg
|
||||
case *App:
|
||||
subApp = arg
|
||||
case []string:
|
||||
prefixes = arg
|
||||
case Handler:
|
||||
handlers = append(handlers, arg)
|
||||
default:
|
||||
|
@ -76,12 +78,18 @@ func (grp *Group) Use(args ...any) Router {
|
|||
}
|
||||
}
|
||||
|
||||
if subApp != nil {
|
||||
grp.mount(prefix, subApp)
|
||||
return grp
|
||||
if len(prefixes) == 0 {
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
grp.app.register([]string{methodUse}, getGroupPath(grp.Prefix, prefix), nil, handlers...)
|
||||
for _, prefix := range prefixes {
|
||||
if subApp != nil {
|
||||
grp.mount(prefix, subApp)
|
||||
return grp
|
||||
}
|
||||
|
||||
grp.app.register([]string{methodUse}, getGroupPath(grp.Prefix, prefix), grp, nil, handlers...)
|
||||
}
|
||||
|
||||
return grp
|
||||
}
|
||||
|
@ -89,8 +97,7 @@ func (grp *Group) Use(args ...any) Router {
|
|||
// Get registers a route for GET methods that requests a representation
|
||||
// of the specified resource. Requests using GET should only retrieve data.
|
||||
func (grp *Group) Get(path string, handler Handler, middleware ...Handler) Router {
|
||||
path = getGroupPath(grp.Prefix, path)
|
||||
return grp.app.Add([]string{MethodGet}, path, handler, middleware...)
|
||||
return grp.Add([]string{MethodGet}, path, handler, middleware...)
|
||||
}
|
||||
|
||||
// Head registers a route for HEAD methods that asks for a response identical
|
||||
|
@ -142,7 +149,7 @@ func (grp *Group) Patch(path string, handler Handler, middleware ...Handler) Rou
|
|||
|
||||
// Add allows you to specify multiple HTTP methods to register a route.
|
||||
func (grp *Group) Add(methods []string, path string, handler Handler, middleware ...Handler) Router {
|
||||
return grp.app.register(methods, getGroupPath(grp.Prefix, path), handler, middleware...)
|
||||
return grp.app.register(methods, getGroupPath(grp.Prefix, path), grp, handler, middleware...)
|
||||
}
|
||||
|
||||
// Static will create a file server serving static files
|
||||
|
@ -152,7 +159,7 @@ func (grp *Group) Static(prefix, root string, config ...Static) Router {
|
|||
|
||||
// All will register the handler on all HTTP methods
|
||||
func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router {
|
||||
grp.Add(intMethod, path, handler, middleware...)
|
||||
_ = grp.Add(grp.app.config.RequestMethods, path, handler, middleware...)
|
||||
return grp
|
||||
}
|
||||
|
||||
|
@ -163,9 +170,17 @@ func (grp *Group) All(path string, handler Handler, middleware ...Handler) Route
|
|||
func (grp *Group) Group(prefix string, handlers ...Handler) Router {
|
||||
prefix = getGroupPath(grp.Prefix, prefix)
|
||||
if len(handlers) > 0 {
|
||||
grp.app.register([]string{methodUse}, prefix, nil, handlers...)
|
||||
_ = grp.app.register([]string{methodUse}, prefix, grp, nil, handlers...)
|
||||
}
|
||||
return grp.app.Group(prefix)
|
||||
|
||||
// Create new group
|
||||
newGrp := &Group{Prefix: prefix, app: grp.app, parentGroup: grp}
|
||||
if err := grp.app.hooks.executeOnGroupHooks(*newGrp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return newGrp
|
||||
|
||||
}
|
||||
|
||||
// Route is used to define routes with a common prefix inside the common function.
|
||||
|
|
286
helpers.go
286
helpers.go
|
@ -75,8 +75,9 @@ func (app *App) quoteString(raw string) string {
|
|||
}
|
||||
|
||||
// Scan stack if other methods match the request
|
||||
func methodExist(c *DefaultCtx) (exist bool) {
|
||||
for i := 0; i < len(intMethod); i++ {
|
||||
func (app *App) methodExist(c *DefaultCtx) (exist bool) {
|
||||
methods := app.config.RequestMethods
|
||||
for i := 0; i < len(methods); i++ {
|
||||
// Skip original method
|
||||
if c.getMethodINT() == i {
|
||||
continue
|
||||
|
@ -107,7 +108,7 @@ func methodExist(c *DefaultCtx) (exist bool) {
|
|||
// We matched
|
||||
exist = true
|
||||
// Add method to Allow header
|
||||
c.Append(HeaderAllow, intMethod[i])
|
||||
c.Append(HeaderAllow, methods[i])
|
||||
// Break stack loop
|
||||
break
|
||||
}
|
||||
|
@ -117,8 +118,9 @@ func methodExist(c *DefaultCtx) (exist bool) {
|
|||
}
|
||||
|
||||
// Scan stack if other methods match the request
|
||||
func methodExistCustom(c CustomCtx) (exist bool) {
|
||||
for i := 0; i < len(intMethod); i++ {
|
||||
func (app *App) methodExistCustom(c CustomCtx) (exist bool) {
|
||||
methods := app.config.RequestMethods
|
||||
for i := 0; i < len(methods); i++ {
|
||||
// Skip original method
|
||||
if c.getMethodINT() == i {
|
||||
continue
|
||||
|
@ -149,7 +151,7 @@ func methodExistCustom(c CustomCtx) (exist bool) {
|
|||
// We matched
|
||||
exist = true
|
||||
// Add method to Allow header
|
||||
c.Append(HeaderAllow, intMethod[i])
|
||||
c.Append(HeaderAllow, methods[i])
|
||||
// Break stack loop
|
||||
break
|
||||
}
|
||||
|
@ -323,42 +325,41 @@ var getBytesImmutable = func(s string) (b []byte) {
|
|||
}
|
||||
|
||||
// HTTP methods and their unique INTs
|
||||
func methodInt(s string) int {
|
||||
switch s {
|
||||
case MethodGet:
|
||||
return 0
|
||||
case MethodHead:
|
||||
return 1
|
||||
case MethodPost:
|
||||
return 2
|
||||
case MethodPut:
|
||||
return 3
|
||||
case MethodDelete:
|
||||
return 4
|
||||
case MethodConnect:
|
||||
return 5
|
||||
case MethodOptions:
|
||||
return 6
|
||||
case MethodTrace:
|
||||
return 7
|
||||
case MethodPatch:
|
||||
return 8
|
||||
default:
|
||||
return -1
|
||||
func (app *App) methodInt(s string) int {
|
||||
// For better performance
|
||||
if len(app.configured.RequestMethods) == 0 {
|
||||
switch s {
|
||||
case MethodGet:
|
||||
return 0
|
||||
case MethodHead:
|
||||
return 1
|
||||
case MethodPost:
|
||||
return 2
|
||||
case MethodPut:
|
||||
return 3
|
||||
case MethodDelete:
|
||||
return 4
|
||||
case MethodConnect:
|
||||
return 5
|
||||
case MethodOptions:
|
||||
return 6
|
||||
case MethodTrace:
|
||||
return 7
|
||||
case MethodPatch:
|
||||
return 8
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP methods slice
|
||||
var intMethod = []string{
|
||||
MethodGet,
|
||||
MethodHead,
|
||||
MethodPost,
|
||||
MethodPut,
|
||||
MethodDelete,
|
||||
MethodConnect,
|
||||
MethodOptions,
|
||||
MethodTrace,
|
||||
MethodPatch,
|
||||
// For method customization
|
||||
for i, v := range app.config.RequestMethods {
|
||||
if s == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// HTTP methods were copied from net/http.
|
||||
|
@ -399,65 +400,73 @@ const (
|
|||
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
||||
)
|
||||
|
||||
// HTTP status codes were copied from net/http.
|
||||
// HTTP status codes were copied from https://github.com/nginx/nginx/blob/67d2a9541826ecd5db97d604f23460210fd3e517/conf/mime.types with the following updates:
|
||||
// - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation
|
||||
// - Add StatusSwitchProxy (306)
|
||||
// NOTE: Keep this list in sync with statusMessage
|
||||
const (
|
||||
StatusContinue = 100 // RFC 7231, 6.2.1
|
||||
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
|
||||
StatusProcessing = 102 // RFC 2518, 10.1
|
||||
StatusEarlyHints = 103 // RFC 8297
|
||||
StatusOK = 200 // RFC 7231, 6.3.1
|
||||
StatusCreated = 201 // RFC 7231, 6.3.2
|
||||
StatusAccepted = 202 // RFC 7231, 6.3.3
|
||||
StatusNonAuthoritativeInformation = 203 // RFC 7231, 6.3.4
|
||||
StatusNoContent = 204 // RFC 7231, 6.3.5
|
||||
StatusResetContent = 205 // RFC 7231, 6.3.6
|
||||
StatusPartialContent = 206 // RFC 7233, 4.1
|
||||
StatusMultiStatus = 207 // RFC 4918, 11.1
|
||||
StatusAlreadyReported = 208 // RFC 5842, 7.1
|
||||
StatusIMUsed = 226 // RFC 3229, 10.4.1
|
||||
StatusMultipleChoices = 300 // RFC 7231, 6.4.1
|
||||
StatusMovedPermanently = 301 // RFC 7231, 6.4.2
|
||||
StatusFound = 302 // RFC 7231, 6.4.3
|
||||
StatusSeeOther = 303 // RFC 7231, 6.4.4
|
||||
StatusNotModified = 304 // RFC 7232, 4.1
|
||||
StatusUseProxy = 305 // RFC 7231, 6.4.5
|
||||
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
|
||||
StatusPermanentRedirect = 308 // RFC 7538, 3
|
||||
StatusBadRequest = 400 // RFC 7231, 6.5.1
|
||||
StatusUnauthorized = 401 // RFC 7235, 3.1
|
||||
StatusPaymentRequired = 402 // RFC 7231, 6.5.2
|
||||
StatusForbidden = 403 // RFC 7231, 6.5.3
|
||||
StatusNotFound = 404 // RFC 7231, 6.5.4
|
||||
StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
|
||||
StatusNotAcceptable = 406 // RFC 7231, 6.5.6
|
||||
StatusProxyAuthRequired = 407 // RFC 7235, 3.2
|
||||
StatusRequestTimeout = 408 // RFC 7231, 6.5.7
|
||||
StatusConflict = 409 // RFC 7231, 6.5.8
|
||||
StatusGone = 410 // RFC 7231, 6.5.9
|
||||
StatusLengthRequired = 411 // RFC 7231, 6.5.10
|
||||
StatusPreconditionFailed = 412 // RFC 7232, 4.2
|
||||
StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
|
||||
StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
|
||||
StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
|
||||
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
|
||||
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
|
||||
StatusTeapot = 418 // RFC 7168, 2.3.3
|
||||
StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2
|
||||
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
|
||||
StatusLocked = 423 // RFC 4918, 11.3
|
||||
StatusFailedDependency = 424 // RFC 4918, 11.4
|
||||
StatusTooEarly = 425 // RFC 8470, 5.2.
|
||||
StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
|
||||
StatusPreconditionRequired = 428 // RFC 6585, 3
|
||||
StatusTooManyRequests = 429 // RFC 6585, 4
|
||||
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
|
||||
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
|
||||
StatusInternalServerError = 500 // RFC 7231, 6.6.1
|
||||
StatusNotImplemented = 501 // RFC 7231, 6.6.2
|
||||
StatusBadGateway = 502 // RFC 7231, 6.6.3
|
||||
StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
|
||||
StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
|
||||
StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
|
||||
StatusContinue = 100 // RFC 9110, 15.2.1
|
||||
StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
|
||||
StatusProcessing = 102 // RFC 2518, 10.1
|
||||
StatusEarlyHints = 103 // RFC 8297
|
||||
|
||||
StatusOK = 200 // RFC 9110, 15.3.1
|
||||
StatusCreated = 201 // RFC 9110, 15.3.2
|
||||
StatusAccepted = 202 // RFC 9110, 15.3.3
|
||||
StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4
|
||||
StatusNoContent = 204 // RFC 9110, 15.3.5
|
||||
StatusResetContent = 205 // RFC 9110, 15.3.6
|
||||
StatusPartialContent = 206 // RFC 9110, 15.3.7
|
||||
StatusMultiStatus = 207 // RFC 4918, 11.1
|
||||
StatusAlreadyReported = 208 // RFC 5842, 7.1
|
||||
StatusIMUsed = 226 // RFC 3229, 10.4.1
|
||||
|
||||
StatusMultipleChoices = 300 // RFC 9110, 15.4.1
|
||||
StatusMovedPermanently = 301 // RFC 9110, 15.4.2
|
||||
StatusFound = 302 // RFC 9110, 15.4.3
|
||||
StatusSeeOther = 303 // RFC 9110, 15.4.4
|
||||
StatusNotModified = 304 // RFC 9110, 15.4.5
|
||||
StatusUseProxy = 305 // RFC 9110, 15.4.6
|
||||
StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused)
|
||||
StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
|
||||
StatusPermanentRedirect = 308 // RFC 9110, 15.4.9
|
||||
|
||||
StatusBadRequest = 400 // RFC 9110, 15.5.1
|
||||
StatusUnauthorized = 401 // RFC 9110, 15.5.2
|
||||
StatusPaymentRequired = 402 // RFC 9110, 15.5.3
|
||||
StatusForbidden = 403 // RFC 9110, 15.5.4
|
||||
StatusNotFound = 404 // RFC 9110, 15.5.5
|
||||
StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6
|
||||
StatusNotAcceptable = 406 // RFC 9110, 15.5.7
|
||||
StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8
|
||||
StatusRequestTimeout = 408 // RFC 9110, 15.5.9
|
||||
StatusConflict = 409 // RFC 9110, 15.5.10
|
||||
StatusGone = 410 // RFC 9110, 15.5.11
|
||||
StatusLengthRequired = 411 // RFC 9110, 15.5.12
|
||||
StatusPreconditionFailed = 412 // RFC 9110, 15.5.13
|
||||
StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14
|
||||
StatusRequestURITooLong = 414 // RFC 9110, 15.5.15
|
||||
StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16
|
||||
StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
|
||||
StatusExpectationFailed = 417 // RFC 9110, 15.5.18
|
||||
StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused)
|
||||
StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20
|
||||
StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21
|
||||
StatusLocked = 423 // RFC 4918, 11.3
|
||||
StatusFailedDependency = 424 // RFC 4918, 11.4
|
||||
StatusTooEarly = 425 // RFC 8470, 5.2.
|
||||
StatusUpgradeRequired = 426 // RFC 9110, 15.5.22
|
||||
StatusPreconditionRequired = 428 // RFC 6585, 3
|
||||
StatusTooManyRequests = 429 // RFC 6585, 4
|
||||
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
|
||||
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
|
||||
|
||||
StatusInternalServerError = 500 // RFC 9110, 15.6.1
|
||||
StatusNotImplemented = 501 // RFC 9110, 15.6.2
|
||||
StatusBadGateway = 502 // RFC 9110, 15.6.3
|
||||
StatusServiceUnavailable = 503 // RFC 9110, 15.6.4
|
||||
StatusGatewayTimeout = 504 // RFC 9110, 15.6.5
|
||||
StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6
|
||||
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
|
||||
StatusInsufficientStorage = 507 // RFC 4918, 11.5
|
||||
StatusLoopDetected = 508 // RFC 5842, 7.2
|
||||
|
@ -467,46 +476,47 @@ const (
|
|||
|
||||
// Errors
|
||||
var (
|
||||
ErrBadRequest = NewError(StatusBadRequest) // RFC 7231, 6.5.1
|
||||
ErrUnauthorized = NewError(StatusUnauthorized) // RFC 7235, 3.1
|
||||
ErrPaymentRequired = NewError(StatusPaymentRequired) // RFC 7231, 6.5.2
|
||||
ErrForbidden = NewError(StatusForbidden) // RFC 7231, 6.5.3
|
||||
ErrNotFound = NewError(StatusNotFound) // RFC 7231, 6.5.4
|
||||
ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // RFC 7231, 6.5.5
|
||||
ErrNotAcceptable = NewError(StatusNotAcceptable) // RFC 7231, 6.5.6
|
||||
ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // RFC 7235, 3.2
|
||||
ErrRequestTimeout = NewError(StatusRequestTimeout) // RFC 7231, 6.5.7
|
||||
ErrConflict = NewError(StatusConflict) // RFC 7231, 6.5.8
|
||||
ErrGone = NewError(StatusGone) // RFC 7231, 6.5.9
|
||||
ErrLengthRequired = NewError(StatusLengthRequired) // RFC 7231, 6.5.10
|
||||
ErrPreconditionFailed = NewError(StatusPreconditionFailed) // RFC 7232, 4.2
|
||||
ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // RFC 7231, 6.5.11
|
||||
ErrRequestURITooLong = NewError(StatusRequestURITooLong) // RFC 7231, 6.5.12
|
||||
ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // RFC 7231, 6.5.13
|
||||
ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // RFC 7233, 4.4
|
||||
ErrExpectationFailed = NewError(StatusExpectationFailed) // RFC 7231, 6.5.14
|
||||
ErrTeapot = NewError(StatusTeapot) // RFC 7168, 2.3.3
|
||||
ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // RFC 7540, 9.1.2
|
||||
ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // RFC 4918, 11.2
|
||||
ErrLocked = NewError(StatusLocked) // RFC 4918, 11.3
|
||||
ErrFailedDependency = NewError(StatusFailedDependency) // RFC 4918, 11.4
|
||||
ErrTooEarly = NewError(StatusTooEarly) // RFC 8470, 5.2.
|
||||
ErrUpgradeRequired = NewError(StatusUpgradeRequired) // RFC 7231, 6.5.15
|
||||
ErrPreconditionRequired = NewError(StatusPreconditionRequired) // RFC 6585, 3
|
||||
ErrTooManyRequests = NewError(StatusTooManyRequests) // RFC 6585, 4
|
||||
ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // RFC 6585, 5
|
||||
ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // RFC 7725, 3
|
||||
ErrInternalServerError = NewError(StatusInternalServerError) // RFC 7231, 6.6.1
|
||||
ErrNotImplemented = NewError(StatusNotImplemented) // RFC 7231, 6.6.2
|
||||
ErrBadGateway = NewError(StatusBadGateway) // RFC 7231, 6.6.3
|
||||
ErrServiceUnavailable = NewError(StatusServiceUnavailable) // RFC 7231, 6.6.4
|
||||
ErrGatewayTimeout = NewError(StatusGatewayTimeout) // RFC 7231, 6.6.5
|
||||
ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // RFC 7231, 6.6.6
|
||||
ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // RFC 2295, 8.1
|
||||
ErrInsufficientStorage = NewError(StatusInsufficientStorage) // RFC 4918, 11.5
|
||||
ErrLoopDetected = NewError(StatusLoopDetected) // RFC 5842, 7.2
|
||||
ErrNotExtended = NewError(StatusNotExtended) // RFC 2774, 7
|
||||
ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // RFC 6585, 6
|
||||
ErrBadRequest = NewError(StatusBadRequest) // 400
|
||||
ErrUnauthorized = NewError(StatusUnauthorized) // 401
|
||||
ErrPaymentRequired = NewError(StatusPaymentRequired) // 402
|
||||
ErrForbidden = NewError(StatusForbidden) // 403
|
||||
ErrNotFound = NewError(StatusNotFound) // 404
|
||||
ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405
|
||||
ErrNotAcceptable = NewError(StatusNotAcceptable) // 406
|
||||
ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407
|
||||
ErrRequestTimeout = NewError(StatusRequestTimeout) // 408
|
||||
ErrConflict = NewError(StatusConflict) // 409
|
||||
ErrGone = NewError(StatusGone) // 410
|
||||
ErrLengthRequired = NewError(StatusLengthRequired) // 411
|
||||
ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412
|
||||
ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413
|
||||
ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414
|
||||
ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415
|
||||
ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416
|
||||
ErrExpectationFailed = NewError(StatusExpectationFailed) // 417
|
||||
ErrTeapot = NewError(StatusTeapot) // 418
|
||||
ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421
|
||||
ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422
|
||||
ErrLocked = NewError(StatusLocked) // 423
|
||||
ErrFailedDependency = NewError(StatusFailedDependency) // 424
|
||||
ErrTooEarly = NewError(StatusTooEarly) // 425
|
||||
ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426
|
||||
ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428
|
||||
ErrTooManyRequests = NewError(StatusTooManyRequests) // 429
|
||||
ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431
|
||||
ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451
|
||||
|
||||
ErrInternalServerError = NewError(StatusInternalServerError) // 500
|
||||
ErrNotImplemented = NewError(StatusNotImplemented) // 501
|
||||
ErrBadGateway = NewError(StatusBadGateway) // 502
|
||||
ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503
|
||||
ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504
|
||||
ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505
|
||||
ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506
|
||||
ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507
|
||||
ErrLoopDetected = NewError(StatusLoopDetected) // 508
|
||||
ErrNotExtended = NewError(StatusNotExtended) // 510
|
||||
ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511
|
||||
)
|
||||
|
||||
// HTTP Headers were copied from net/http.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
16
mount.go
16
mount.go
|
@ -44,8 +44,10 @@ func (app *App) mount(prefix string, fiber *App) Router {
|
|||
|
||||
// Support for configs of mounted-apps and sub-mounted-apps
|
||||
for mountedPrefixes, subApp := range fiber.mountFields.appList {
|
||||
subApp.mountFields.mountPath = prefix + mountedPrefixes
|
||||
app.mountFields.appList[prefix+mountedPrefixes] = subApp
|
||||
path := getGroupPath(prefix, mountedPrefixes)
|
||||
|
||||
subApp.mountFields.mountPath = path
|
||||
app.mountFields.appList[path] = subApp
|
||||
}
|
||||
|
||||
// Execute onMount hooks
|
||||
|
@ -70,8 +72,10 @@ func (grp *Group) mount(prefix string, fiber *App) Router {
|
|||
|
||||
// Support for configs of mounted-apps and sub-mounted-apps
|
||||
for mountedPrefixes, subApp := range fiber.mountFields.appList {
|
||||
subApp.mountFields.mountPath = groupPath + mountedPrefixes
|
||||
grp.app.mountFields.appList[groupPath+mountedPrefixes] = subApp
|
||||
path := getGroupPath(groupPath, mountedPrefixes)
|
||||
|
||||
subApp.mountFields.mountPath = path
|
||||
grp.app.mountFields.appList[path] = subApp
|
||||
}
|
||||
|
||||
// Execute onMount hooks
|
||||
|
@ -107,7 +111,7 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) {
|
|||
}
|
||||
|
||||
if len(parent) > 0 {
|
||||
prefix = parent[0] + prefix
|
||||
prefix = getGroupPath(parent[0], prefix)
|
||||
}
|
||||
|
||||
if _, ok := app.mountFields.appList[prefix]; !ok {
|
||||
|
@ -131,7 +135,7 @@ func (app *App) addSubAppsRoutes(appList map[string]*App, parent ...string) {
|
|||
}
|
||||
|
||||
if len(parent) > 0 {
|
||||
prefix = parent[0] + prefix
|
||||
prefix = getGroupPath(parent[0], prefix)
|
||||
}
|
||||
|
||||
// add routes
|
||||
|
|
|
@ -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
10
path.go
|
@ -437,10 +437,12 @@ func (routeParser *routeParser) getMatch(detectionPath, path string, params *[ma
|
|||
// take over the params positions
|
||||
params[paramsIterator] = path[:i]
|
||||
|
||||
// check constraint
|
||||
for _, c := range segment.Constraints {
|
||||
if matched := c.CheckConstraint(params[paramsIterator]); !matched {
|
||||
return false
|
||||
if !(segment.IsOptional && i == 0) {
|
||||
// check constraint
|
||||
for _, c := range segment.Constraints {
|
||||
if matched := c.CheckConstraint(params[paramsIterator]); !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
path_test.go
12
path_test.go
|
@ -598,6 +598,12 @@ func Test_Path_matchParams(t *testing.T) {
|
|||
{url: "/api/v1/2005-1101/paach", params: nil, match: false},
|
||||
{url: "/api/v1/2005-11-01/peach", params: []string{"2005-11-01", "peach"}, match: true},
|
||||
})
|
||||
testCase("/api/v1/:param<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},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
22
router.go
22
router.go
|
@ -50,6 +50,7 @@ type Route struct {
|
|||
root bool // Path equals '/'
|
||||
path string // Prettified path
|
||||
routeParser routeParser // Parameter parser
|
||||
group *Group // Group instance. used for routes in groups
|
||||
|
||||
// Public fields
|
||||
Method string `json:"method"` // HTTP method
|
||||
|
@ -136,7 +137,7 @@ func (app *App) nextCustom(c CustomCtx) (match bool, err error) {
|
|||
|
||||
// If no match, scan stack again if other methods match the request
|
||||
// Moved from app.handler because middleware may break the route chain
|
||||
if !c.getMatched() && methodExistCustom(c) {
|
||||
if !c.getMatched() && app.methodExistCustom(c) {
|
||||
err = ErrMethodNotAllowed
|
||||
}
|
||||
return
|
||||
|
@ -184,7 +185,7 @@ func (app *App) next(c *DefaultCtx) (match bool, err error) {
|
|||
|
||||
// If no match, scan stack again if other methods match the request
|
||||
// Moved from app.handler because middleware may break the route chain
|
||||
if !c.matched && methodExist(c) {
|
||||
if !c.matched && app.methodExist(c) {
|
||||
err = ErrMethodNotAllowed
|
||||
}
|
||||
return
|
||||
|
@ -202,7 +203,7 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) {
|
|||
defer app.ReleaseCtx(c)
|
||||
|
||||
// handle invalid http method directly
|
||||
if methodInt(c.Method()) == -1 {
|
||||
if app.methodInt(c.Method()) == -1 {
|
||||
_ = c.SendStatus(StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
@ -266,7 +267,7 @@ func (app *App) copyRoute(route *Route) *Route {
|
|||
}
|
||||
}
|
||||
|
||||
func (app *App) register(methods []string, pathRaw string, handler Handler, middleware ...Handler) Router {
|
||||
func (app *App) register(methods []string, pathRaw string, group *Group, handler Handler, middleware ...Handler) Router {
|
||||
handlers := middleware
|
||||
if handler != nil {
|
||||
handlers = append(handlers, handler)
|
||||
|
@ -277,7 +278,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
|
|||
// Uppercase HTTP methods
|
||||
method = utils.ToUpper(method)
|
||||
// Check if the HTTP method is valid unless it's USE
|
||||
if method != methodUse && methodInt(method) == -1 {
|
||||
if method != methodUse && app.methodInt(method) == -1 {
|
||||
panic(fmt.Sprintf("add: invalid http method %s\n", method))
|
||||
}
|
||||
// A route requires atleast one ctx handler
|
||||
|
@ -324,6 +325,9 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
|
|||
routeParser: parsedPretty,
|
||||
Params: parsedRaw.params,
|
||||
|
||||
// Group data
|
||||
group: group,
|
||||
|
||||
// Public data
|
||||
Path: pathRaw,
|
||||
Method: method,
|
||||
|
@ -335,7 +339,7 @@ func (app *App) register(methods []string, pathRaw string, handler Handler, midd
|
|||
// Middleware route matches all HTTP methods
|
||||
if isUse {
|
||||
// Add route to all HTTP methods stack
|
||||
for _, m := range intMethod {
|
||||
for _, m := range app.config.RequestMethods {
|
||||
// Create a route copy to avoid duplicates during compression
|
||||
r := route
|
||||
app.addRoute(m, &r)
|
||||
|
@ -496,7 +500,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
|||
}
|
||||
|
||||
// Get unique HTTP method identifier
|
||||
m := methodInt(method)
|
||||
m := app.methodInt(method)
|
||||
|
||||
// prevent identically route registration
|
||||
l := len(app.stack[m])
|
||||
|
@ -530,7 +534,7 @@ func (app *App) buildTree() *App {
|
|||
}
|
||||
|
||||
// loop all the methods and stacks and create the prefix tree
|
||||
for m := range intMethod {
|
||||
for m := range app.config.RequestMethods {
|
||||
tsMap := make(map[string][]*Route)
|
||||
for _, route := range app.stack[m] {
|
||||
treePath := ""
|
||||
|
@ -544,7 +548,7 @@ func (app *App) buildTree() *App {
|
|||
}
|
||||
|
||||
// loop the methods and tree stacks and add global stack and sort everything
|
||||
for m := range intMethod {
|
||||
for m := range app.config.RequestMethods {
|
||||
tsMap := app.treeStack[m]
|
||||
for treePart := range tsMap {
|
||||
if treePart != "" {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue