mirror of
https://github.com/gofiber/fiber.git
synced 2025-07-08 03:28:34 +00:00
v1.9.6 (#360)
**🚀 Fiber `v1.9.6`** Special thanks to @renanbastos93 & @renewerner87 for optimizing the current router. Help use translate our API documentation by [clicking here](https://crowdin.com/project/gofiber) 🔥 New - `AcquireCtx` / `ReleaseCtx` The Ctx pool is now accessible for third-party packages - Fiber docs merged [Russian](https://docs.gofiber.io/v/ru/) translations **84%** - Fiber docs merged [Spanish](https://docs.gofiber.io/v/es/) translations **65%** - Fiber docs merged [French](https://docs.gofiber.io/v/fr/) translations **40%** - Fiber docs merged [German](https://docs.gofiber.io/v/de/) translations **32%** - Fiber docs merged [Portuguese](https://docs.gofiber.io/v/pt/) translations **24%** 🩹 Fixes - Hotfix for interpolated params in nested routes https://github.com/gofiber/fiber/issues/354 - Some `Ctx` methods didn't work correctly when called without an `*App` pointer. - `ctx.Vary` sometimes added duplicates to the response header - Improved router by ditching regexp and increased performance by **817%** without allocations. ```go // Tested with 350 github API routes Benchmark_Router_OLD-4 614 2467460 ns/op 68902 B/op 600 allocs/op Benchmark_Router_NEW-4 3429 302033 ns/op 0 B/op 0 allocs/op ``` 🧹 Updates - Add context benchmarks - Remove some unnecessary functions from `utils` - Add router & param test cases - Add new coffee supporters to readme - Add third party middlewares to readme - Add more comments to source code - Cleanup some old helper functions 🧬 Middleware - [gofiber/adaptor](https://github.com/gofiber/adaptor) `v0.0.1` Converter for net/http handlers to/from Fiber handlers - [gofiber/session](https://github.com/gofiber/session) `v1.0.0` big improvements and support for storage providers - [gofiber/logger](https://github.com/gofiber/logger) `v0.0.6` supports `${error}` param - [gofiber/embed](https://github.com/gofiber/embed) `v0.0.9` minor improvements and support for directory browsing Co-authored-by: ReneWerner87 <renewerner87@googlemail.com>
This commit is contained in:
parent
6e58bfcde3
commit
99f95b2561
30
app_test.go
30
app_test.go
@ -20,6 +20,33 @@ func testStatus200(t *testing.T, app *App, url string, method string) {
|
||||
assertEqual(t, 200, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
func Test_Nested_Params(t *testing.T) {
|
||||
app := New()
|
||||
|
||||
app.Get("/test", func(c *Ctx) {
|
||||
t.Log(c.Route().Path)
|
||||
c.Status(400).Send("Should move on")
|
||||
})
|
||||
app.Get("/test/:param", func(c *Ctx) {
|
||||
t.Log(c.Route().Path)
|
||||
c.Status(400).Send("Should move on")
|
||||
})
|
||||
app.Get("/test/:param/test", func(c *Ctx) {
|
||||
t.Log(c.Route().Path)
|
||||
c.Status(400).Send("Should move on")
|
||||
})
|
||||
app.Get("/test/:param/test/:param2", func(c *Ctx) {
|
||||
t.Log(c.Route().Path)
|
||||
c.Status(200).Send("Good job")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/test/john/test/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
|
||||
assertEqual(t, nil, err, "app.Test(req)")
|
||||
assertEqual(t, 200, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
func Test_Raw(t *testing.T) {
|
||||
app := New()
|
||||
app.Get("/", func(c *Ctx) {
|
||||
@ -65,9 +92,6 @@ func Test_Methods(t *testing.T) {
|
||||
app.Connect("/:john?/:doe?", dummyHandler)
|
||||
testStatus200(t, app, "/john/doe", "CONNECT")
|
||||
|
||||
app.Connect("/:john?/:doe?", dummyHandler)
|
||||
testStatus200(t, app, "/john/doe", "CONNECT")
|
||||
|
||||
app.Put("/:john?/:doe?", dummyHandler)
|
||||
testStatus200(t, app, "/john/doe", "CONNECT")
|
||||
|
||||
|
33
params.go
33
params.go
@ -13,8 +13,8 @@ import (
|
||||
|
||||
// paramsParser holds the path segments and param names
|
||||
type parsedParams struct {
|
||||
Segs []paramSeg
|
||||
Keys []string
|
||||
Segs []paramSeg
|
||||
Params []string
|
||||
}
|
||||
|
||||
// paramsSeg holds the segment metadata
|
||||
@ -28,11 +28,10 @@ type paramSeg struct {
|
||||
|
||||
var paramsDummy = make([]string, 100, 100)
|
||||
|
||||
const wildcardParam string = "*"
|
||||
|
||||
// New ...
|
||||
func parseParams(pattern string) (p parsedParams) {
|
||||
if pattern[0] != '/' {
|
||||
pattern = "/" + pattern
|
||||
}
|
||||
var patternCount int
|
||||
aPattern := []string{""}
|
||||
if pattern != "" {
|
||||
@ -53,7 +52,7 @@ func parseParams(pattern string) (p parsedParams) {
|
||||
out[segIndex] = paramSeg{
|
||||
Param: paramTrimmer(aPattern[i]),
|
||||
IsParam: true,
|
||||
IsOptional: aPattern[i] == "*" || aPattern[i][partLen-1] == '?',
|
||||
IsOptional: aPattern[i] == wildcardParam || aPattern[i][partLen-1] == '?',
|
||||
}
|
||||
params = append(params, out[segIndex].Param)
|
||||
} else {
|
||||
@ -75,14 +74,14 @@ func parseParams(pattern string) (p parsedParams) {
|
||||
}
|
||||
out[segIndex-1].IsLast = true
|
||||
|
||||
p = parsedParams{Segs: out[:segIndex:segIndex], Keys: params}
|
||||
// fmt.Printf("%+v\n", p)
|
||||
p = parsedParams{Segs: out[:segIndex:segIndex], Params: params}
|
||||
//fmt.Printf("%+v\n", p)
|
||||
return
|
||||
}
|
||||
|
||||
// Match ...
|
||||
func (p *parsedParams) matchParams(s string) ([]string, bool) {
|
||||
lenKeys := len(p.Keys)
|
||||
lenKeys := len(p.Params)
|
||||
params := paramsDummy[0:lenKeys:lenKeys]
|
||||
var i, j, paramsIterator, partLen int
|
||||
if len(s) > 0 {
|
||||
@ -93,11 +92,13 @@ func (p *parsedParams) matchParams(s string) ([]string, bool) {
|
||||
// check parameter
|
||||
if segment.IsParam {
|
||||
// determine parameter length
|
||||
if segment.IsLast {
|
||||
i = partLen
|
||||
} else if segment.Param == "*" {
|
||||
// for the expressjs behavior -> "/api/*/:param" - "/api/joker/batman/robin/1" -> "joker/batman/robin", "1"
|
||||
i = findCharPos(s, '/', strings.Count(s, "/")-(len(p.Segs)-(index+1))+1)
|
||||
if segment.Param == wildcardParam {
|
||||
if segment.IsLast {
|
||||
i = partLen
|
||||
} else {
|
||||
// for the expressjs behavior -> "/api/*/:param" - "/api/joker/batman/robin/1" -> "joker/batman/robin", "1"
|
||||
i = findCharPos(s, '/', strings.Count(s, "/")-(len(p.Segs)-(index+1))+1)
|
||||
}
|
||||
} else {
|
||||
i = strings.IndexByte(s, '/')
|
||||
}
|
||||
@ -129,10 +130,12 @@ func (p *parsedParams) matchParams(s string) ([]string, bool) {
|
||||
s = s[j:]
|
||||
}
|
||||
}
|
||||
if len(s) != 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return params, true
|
||||
}
|
||||
|
||||
func paramTrimmer(param string) string {
|
||||
start := 0
|
||||
end := len(param)
|
||||
|
@ -168,7 +168,7 @@ func (app *App) registerMethod(method, path string, handlers ...func(*Ctx)) {
|
||||
|
||||
Path: path,
|
||||
Method: method,
|
||||
Params: isParsed.Keys,
|
||||
Params: isParsed.Params,
|
||||
Handler: handlers[i],
|
||||
}
|
||||
if method == "*" {
|
||||
|
1154
router_bench_test.go
1154
router_bench_test.go
File diff suppressed because it is too large
Load Diff
@ -13,32 +13,17 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// params testing
|
||||
|
||||
type testCase struct {
|
||||
type testcase struct {
|
||||
uri string
|
||||
params []string
|
||||
ok bool
|
||||
}
|
||||
|
||||
func Test_With_Starting_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/*"),
|
||||
[]testCase{
|
||||
{uri: "/api/v1/entity", params: []string{"api/v1/entity"}, ok: true},
|
||||
{uri: "/api/v1/entity/", params: []string{"api/v1/entity/"}, ok: true},
|
||||
{uri: "/api/v1/entity/1", params: []string{"api/v1/entity/1"}, ok: true},
|
||||
{uri: "/", params: []string{""}, ok: true},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func Test_With_Param_And_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/:param/*"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1/entity", params: []string{"entity", ""}, ok: true},
|
||||
{uri: "/api/v1/entity/", params: []string{"entity", ""}, ok: true},
|
||||
{uri: "/api/v1/entity/1", params: []string{"entity", "1"}, ok: true},
|
||||
@ -53,7 +38,7 @@ func Test_With_A_Param_Optional(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/:param?"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1", params: []string{""}, ok: true},
|
||||
{uri: "/api/v1/", params: []string{""}, ok: true},
|
||||
{uri: "/api/v1/optional", params: []string{"optional"}, ok: true},
|
||||
@ -68,7 +53,7 @@ func Test_With_With_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/*"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1", params: []string{""}, ok: true},
|
||||
{uri: "/api/v1/", params: []string{""}, ok: true},
|
||||
{uri: "/api/v1/entity", params: []string{"entity"}, ok: true},
|
||||
@ -83,8 +68,9 @@ func Test_With_With_Param(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/:param"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1/entity", params: []string{"entity"}, ok: true},
|
||||
{uri: "/api/v1/entity/8728382", params: nil, ok: false},
|
||||
{uri: "/api/v1", params: nil, ok: false},
|
||||
{uri: "/api/v1/", params: nil, ok: false},
|
||||
},
|
||||
@ -95,7 +81,7 @@ func Test_With_Without_A_Param_Or_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/const"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1/const", params: []string{}, ok: true},
|
||||
{uri: "/api/v1", params: nil, ok: false},
|
||||
{uri: "/api/v1/", params: nil, ok: false},
|
||||
@ -107,7 +93,7 @@ func Test_With_With_A_Param_And_Wildcard_Differents_Positions(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/v1/:param/abc/*"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/v1/well/abc/wildcard", params: []string{"well", "wildcard"}, ok: true},
|
||||
{uri: "/api/v1/well/abc/", params: []string{"well", ""}, ok: true},
|
||||
{uri: "/api/v1/well/abc", params: []string{"well", ""}, ok: true},
|
||||
@ -119,7 +105,7 @@ func Test_With_With_Params_And_Optional(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/:day/:month?/:year?"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/1", params: []string{"1", "", ""}, ok: true},
|
||||
{uri: "/api/1/", params: []string{"1", "", ""}, ok: true},
|
||||
{uri: "/api/1/2", params: []string{"1", "2", ""}, ok: true},
|
||||
@ -132,7 +118,7 @@ func Test_With_With_Simple_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/*"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/", params: []string{""}, ok: true},
|
||||
{uri: "/api/joker", params: []string{"joker"}, ok: true},
|
||||
{uri: "/api", params: []string{""}, ok: true},
|
||||
@ -146,7 +132,7 @@ func Test_With_With_Wildcard_And_Optional(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/*/:param?"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/", params: []string{"", ""}, ok: true},
|
||||
{uri: "/api/joker", params: []string{"joker", ""}, ok: true},
|
||||
{uri: "/api/joker/batman", params: []string{"joker", "batman"}, ok: true},
|
||||
@ -160,7 +146,7 @@ func Test_With_With_Wildcard_And_Param(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/*/:param"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/test/abc", params: []string{"test", "abc"}, ok: true},
|
||||
{uri: "/api/joker/batman", params: []string{"joker", "batman"}, ok: true},
|
||||
{uri: "/api/joker/batman/robin", params: []string{"joker/batman", "robin"}, ok: true},
|
||||
@ -173,7 +159,7 @@ func Test_With_With_Wildcard_And_2Params(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/api/*/:param/:param2"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api/test/abc", params: nil, ok: false},
|
||||
{uri: "/api/joker/batman", params: nil, ok: false},
|
||||
{uri: "/api/joker/batman/robin", params: []string{"joker", "batman", "robin"}, ok: true},
|
||||
@ -187,7 +173,18 @@ func Test_With_With_Simple_Path(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/api", params: nil, ok: false},
|
||||
{uri: "", params: []string{}, ok: true},
|
||||
{uri: "/", params: []string{}, ok: true},
|
||||
},
|
||||
)
|
||||
}
|
||||
func Test_With_With_Empty_Path(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams(""),
|
||||
[]testcase{
|
||||
{uri: "/api", params: nil, ok: false},
|
||||
{uri: "", params: []string{}, ok: true},
|
||||
{uri: "/", params: []string{}, ok: true},
|
||||
@ -199,7 +196,7 @@ func Test_With_With_FileName(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/config/abc.json"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/config/abc.json", params: []string{}, ok: true},
|
||||
{uri: "config/abc.json", params: nil, ok: false},
|
||||
{uri: "/config/efg.json", params: nil, ok: false},
|
||||
@ -212,7 +209,7 @@ func Test_With_With_FileName_And_Wildcard(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/config/*.json"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "/config/abc.json", params: []string{"abc.json"}, ok: true},
|
||||
{uri: "/config/efg.json", params: []string{"efg.json"}, ok: true},
|
||||
//{uri: "/config/efg.csv", params: nil, ok: false},// doesn`t work, current: params: "efg.csv", true
|
||||
@ -226,14 +223,14 @@ func Test_With_With_Simple_Path_And_NoMatch(t *testing.T) {
|
||||
checkCases(
|
||||
t,
|
||||
parseParams("/xyz"),
|
||||
[]testCase{
|
||||
[]testcase{
|
||||
{uri: "xyz", params: nil, ok: false},
|
||||
{uri: "xyz/", params: nil, ok: false},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func checkCases(tParent *testing.T, parser parsedParams, tcases []testCase) {
|
||||
func checkCases(tParent *testing.T, parser parsedParams, tcases []testcase) {
|
||||
for _, tcase := range tcases {
|
||||
tParent.Run(fmt.Sprintf("%+v", tcase), func(t *testing.T) {
|
||||
params, ok := parser.matchParams(tcase.uri)
|
||||
|
Loading…
x
Reference in New Issue
Block a user