**🚀 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:
Fenny 2020-05-11 13:42:42 +02:00 committed by GitHub
parent 6e58bfcde3
commit 99f95b2561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 652 additions and 628 deletions

View File

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

View File

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

View File

@ -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 == "*" {

File diff suppressed because it is too large Load Diff

View File

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