diff --git a/.github/README.md b/.github/README.md index 64bc6e37f1..15e55489b6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -665,3 +665,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_ckb.md b/.github/README_ckb.md index 4b12e914e4..22cf3ecac6 100644 --- a/.github/README_ckb.md +++ b/.github/README_ckb.md @@ -665,3 +665,4 @@ func main() { - [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) diff --git a/.github/README_de.md b/.github/README_de.md index 9f3dd5587e..aa9c124145 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -635,3 +635,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_es.md b/.github/README_es.md index c50e524b0c..4a986cd360 100644 --- a/.github/README_es.md +++ b/.github/README_es.md @@ -635,3 +635,5 @@ Copyright (c) 2019-presente [Fenny](https://github.com/fenny) y [contribuyentes] - [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) + diff --git a/.github/README_fa.md b/.github/README_fa.md index ad2ddbbadd..d7ffeafdd9 100644 --- a/.github/README_fa.md +++ b/.github/README_fa.md @@ -794,3 +794,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_fr.md b/.github/README_fr.md index 0dcc691089..cc4973cb18 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -637,3 +637,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_he.md b/.github/README_he.md index 6708d6fcae..2473a1bd01 100644 --- a/.github/README_he.md +++ b/.github/README_he.md @@ -810,4 +810,5 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_id.md b/.github/README_id.md index e83ceee61b..e60f8f2549 100644 --- a/.github/README_id.md +++ b/.github/README_id.md @@ -638,3 +638,5 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) + diff --git a/.github/README_it.md b/.github/README_it.md index a752de5455..9bcf06b104 100644 --- a/.github/README_it.md +++ b/.github/README_it.md @@ -661,3 +661,4 @@ Copyright (c) 2019-ora [Fenny](https://github.com/fenny) e [Contributors](https: - [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) diff --git a/.github/README_ja.md b/.github/README_ja.md index 68d4bcd4bc..d936c9bbf3 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -641,3 +641,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_ko.md b/.github/README_ko.md index ddc6c07a8c..4c57ff0f25 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -641,3 +641,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_nl.md b/.github/README_nl.md index a52f32d916..c52a1d25ed 100644 --- a/.github/README_nl.md +++ b/.github/README_nl.md @@ -641,3 +641,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_pt.md b/.github/README_pt.md index 3e818406a2..9cb4aa7ad0 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -637,3 +637,4 @@ O logo oficial foi criado por [Vic Shóstak](https://github.com/koddr) e distrib - [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) diff --git a/.github/README_ru.md b/.github/README_ru.md index eee845895c..022ca6767e 100644 --- a/.github/README_ru.md +++ b/.github/README_ru.md @@ -644,3 +644,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_sa.md b/.github/README_sa.md index 4518eea382..68772b1d5c 100644 --- a/.github/README_sa.md +++ b/.github/README_sa.md @@ -702,3 +702,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_tr.md b/.github/README_tr.md index ba10a4cc9c..fc59090e08 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -635,3 +635,4 @@ Telif (c) 2019-günümüz [Fenny](https://github.com/fenny) ve [Contributors](ht - [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) diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md index d54c56149c..3c9407633c 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -637,3 +637,4 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors]( - [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) diff --git a/.github/README_zh-TW.md b/.github/README_zh-TW.md index 5d8c8ba93c..1315f3cfa7 100644 --- a/.github/README_zh-TW.md +++ b/.github/README_zh-TW.md @@ -635,3 +635,4 @@ Fiber 是一個以贊助維生的開源專案,像是: 網域、gitbook、netli - [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) diff --git a/.github/testdata/template.html b/.github/testdata/template.html deleted file mode 100644 index 131044d79b..0000000000 --- a/.github/testdata/template.html +++ /dev/null @@ -1 +0,0 @@ -

{{.Title}}

\ No newline at end of file diff --git a/.github/testdata/template.tmpl b/.github/testdata/template.tmpl new file mode 100644 index 0000000000..ab6e56044d --- /dev/null +++ b/.github/testdata/template.tmpl @@ -0,0 +1 @@ +

{{.Title}} {{.Summary}}

\ No newline at end of file diff --git a/app_test.go b/app_test.go index a9ec4d01da..d33994e861 100644 --- a/app_test.go +++ b/app_test.go @@ -779,7 +779,7 @@ func Test_App_Static_Prefix(t *testing.T) { app.Static("/prefix", "./.github/testdata") - req = httptest.NewRequest(MethodGet, "/prefix/template.html", nil) + req = httptest.NewRequest(MethodGet, "/prefix/index.html", nil) resp, err = app.Test(req) utils.AssertEqual(t, nil, err, "app.Test(req)") utils.AssertEqual(t, 200, resp.StatusCode, "Status code") @@ -1146,7 +1146,7 @@ func Test_App_ListenTLS_Prefork(t *testing.T) { app := New(Config{DisableStartupMessage: true, Prefork: true}) // invalid key file content - utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.html") == nil) + utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.tmpl") == nil) utils.AssertEqual(t, nil, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")) } diff --git a/ctx.go b/ctx.go index efb9b312f1..935056304b 100644 --- a/ctx.go +++ b/ctx.go @@ -24,6 +24,7 @@ import ( "time" "github.com/gofiber/fiber/v2/internal/bytebufferpool" + "github.com/gofiber/fiber/v2/internal/dictpool" "github.com/gofiber/fiber/v2/internal/go-json" "github.com/gofiber/fiber/v2/internal/schema" "github.com/gofiber/fiber/v2/utils" @@ -62,6 +63,7 @@ type Ctx struct { values [maxParams]string // Route parameter values fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx matched bool // Non use route matched + viewBindMap *dictpool.Dict // Default view map to bind template engine } // Range data for c.Range @@ -137,6 +139,9 @@ func (app *App) ReleaseCtx(c *Ctx) { // Reset values c.route = nil c.fasthttp = nil + if c.viewBindMap != nil { + dictpool.ReleaseDict(c.viewBindMap) + } app.pool.Put(c) } @@ -1060,6 +1065,20 @@ func (c *Ctx) Redirect(location string, status ...int) error { return nil } +// Add vars to default view var map binding to template engine. +// Variables are read by the Render method and may be overwritten. +func (c *Ctx) Bind(vars Map) error { + // init viewBindMap - lazy map + if c.viewBindMap == nil { + c.viewBindMap = dictpool.AcquireDict() + } + for k, v := range vars { + c.viewBindMap.Set(k, v) + } + + return nil +} + // get URL location from route using parameters func (c *Ctx) getLocationFromRoute(route Route, params Map) (string, error) { buf := bytebufferpool.Get() @@ -1113,25 +1132,8 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error { buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) - // Check if the PassLocalsToViews option is enabled (By default it is disabled) - if c.app.config.PassLocalsToViews { - // Safely cast the bind interface to a map - bindMap, ok := bind.(Map) - // Check if the bind is a map - if ok { - // Loop through each local and set it in the map - c.fasthttp.VisitUserValues(func(key []byte, val interface{}) { - // check if bindMap doesn't contain the key - if _, ok := bindMap[string(key)]; !ok { - // Set the key and value in the bindMap - bindMap[string(key)] = val - } - }) - // set the original bind to the map - bind = bindMap - } - - } + // Pass-locals-to-views & bind + c.renderExtensions(bind) rendered := false for prefix, app := range c.app.appList { @@ -1179,6 +1181,29 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error { return err } +func (c *Ctx) renderExtensions(bind interface{}) { + if bindMap, ok := bind.(Map); ok { + // Bind view map + if c.viewBindMap != nil { + for _, v := range c.viewBindMap.D { + bindMap[v.Key] = v.Value + } + } + + // Check if the PassLocalsToViews option is enabled (by default it is disabled) + if c.app.config.PassLocalsToViews { + // Loop through each local and set it in the map + c.fasthttp.VisitUserValues(func(key []byte, val interface{}) { + // check if bindMap doesn't contain the key + if _, ok := bindMap[utils.UnsafeString(key)]; !ok { + // Set the key and value in the bindMap + bindMap[utils.UnsafeString(key)] = val + } + }) + } + } +} + // Route returns the matched Route struct. func (c *Ctx) Route() *Route { if c.route == nil { diff --git a/ctx_test.go b/ctx_test.go index 333bdb6b64..1c082f01bd 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -2140,7 +2140,7 @@ func Test_Ctx_Render(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - err := c.Render("./.github/testdata/template.html", Map{ + err := c.Render("./.github/testdata/index.tmpl", Map{ "Title": "Hello, World!", }) @@ -2219,7 +2219,7 @@ func Test_Ctx_RenderWithoutLocals(t *testing.T) { c.Locals("Title", "Hello, World!") defer app.ReleaseCtx(c) - err := c.Render("./.github/testdata/template.html", Map{}) + err := c.Render("./.github/testdata/index.tmpl", Map{}) buf := bytebufferpool.Get() _, _ = buf.WriteString("overwrite") @@ -2238,7 +2238,7 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { c.Locals("Title", "Hello, World!") defer app.ReleaseCtx(c) - err := c.Render("./.github/testdata/template.html", Map{}) + err := c.Render("./.github/testdata/index.tmpl", Map{}) buf := bytebufferpool.Get() _, _ = buf.WriteString("overwrite") @@ -2248,27 +2248,149 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) } + +func Test_Ctx_RenderWithBind(t *testing.T) { + t.Parallel() + + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Bind(Map{ + "Title": "Hello, World!", + }) + defer app.ReleaseCtx(c) + err := c.Render("./.github/testdata/index.tmpl", Map{}) + + buf := bytebufferpool.Get() + _, _ = buf.WriteString("overwrite") + defer bytebufferpool.Put(buf) + + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) + +} + +func Test_Ctx_RenderWithBindLocals(t *testing.T) { + t.Parallel() + + app := New(Config{ + PassLocalsToViews: true, + }) + + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Bind(Map{ + "Title": "Hello, World!", + }) + + c.Locals("Summary", "Test") + + defer app.ReleaseCtx(c) + err := c.Render("./.github/testdata/template.tmpl", Map{}) + + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "

Hello, World! Test

", string(c.Response().Body())) + +} + func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { t.Parallel() + engine := &testTemplateEngine{} + err := engine.Load() app := New(Config{ PassLocalsToViews: true, + Views: engine, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Locals("Title", "This is a test.") defer app.ReleaseCtx(c) - err := c.Render("./.github/testdata/template.html", Map{ + err = c.Render("index.tmpl", Map{ "Title": "Hello, World!", }) - buf := bytebufferpool.Get() - _, _ = buf.WriteString("overwrite") - defer bytebufferpool.Put(buf) - utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "

Hello, World!

", string(c.Response().Body())) } +func Benchmark_Ctx_RenderWithLocalsAndBinding(b *testing.B) { + engine := &testTemplateEngine{} + err := engine.Load() + utils.AssertEqual(b, nil, err) + app := New(Config{ + PassLocalsToViews: true, + Views: engine, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Bind(Map{ + "Title": "Hello, World!", + }) + c.Locals("Summary", "Test") + + defer app.ReleaseCtx(c) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + err = c.Render("template.tmpl", Map{}) + } + + utils.AssertEqual(b, nil, err) + utils.AssertEqual(b, "

Hello, World! Test

", string(c.Response().Body())) +} + +func Benchmark_Ctx_RenderLocals(b *testing.B) { + engine := &testTemplateEngine{} + err := engine.Load() + utils.AssertEqual(b, nil, err) + app := New(Config{ + PassLocalsToViews: true, + }) + app.config.Views = engine + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Locals("Title", "Hello, World!") + + defer app.ReleaseCtx(c) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + err = c.Render("index.tmpl", Map{}) + } + + utils.AssertEqual(b, nil, err) + utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) +} + +func Benchmark_Ctx_RenderBind(b *testing.B) { + engine := &testTemplateEngine{} + err := engine.Load() + utils.AssertEqual(b, nil, err) + app := New() + app.config.Views = engine + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Bind(Map{ + "Title": "Hello, World!", + }) + + defer app.ReleaseCtx(c) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + err = c.Render("index.tmpl", Map{}) + } + + utils.AssertEqual(b, nil, err) + utils.AssertEqual(b, "

Hello, World!

", string(c.Response().Body())) +} + // go test -run Test_Ctx_RestartRouting func Test_Ctx_RestartRouting(t *testing.T) { app := New() diff --git a/internal/dictpool/LICENSE b/internal/dictpool/LICENSE new file mode 100644 index 0000000000..2b440abc7b --- /dev/null +++ b/internal/dictpool/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020-present Sergio Andres Virviescas Santana + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/internal/dictpool/dict.go b/internal/dictpool/dict.go new file mode 100644 index 0000000000..39b740d2f3 --- /dev/null +++ b/internal/dictpool/dict.go @@ -0,0 +1,164 @@ +package dictpool + +import ( + "sort" + + "github.com/gofiber/fiber/v2/utils" +) + +func (d *Dict) allocKV() *KV { + n := len(d.D) + + if cap(d.D) > n { + d.D = d.D[:n+1] + } else { + d.D = append(d.D, KV{}) + } + + return &d.D[n] +} + +func (d *Dict) append(key string, value interface{}) { + kv := d.allocKV() + kv.Key = key + kv.Value = value +} + +func (d *Dict) indexOf(key string) int { + n := len(d.D) + + if d.BinarySearch { + idx := sort.Search(n, func(i int) bool { + return key <= d.D[i].Key + }) + + if idx < n && d.D[idx].Key == key { + return idx + } + } else { + for i := 0; i < n; i++ { + if d.D[i].Key == key { + return i + } + } + } + + return -1 +} + +// Len is the number of elements in the Dict. +func (d *Dict) Len() int { + return len(d.D) +} + +// Swap swaps the elements with indexes i and j. +func (d *Dict) Swap(i, j int) { + iKey, iValue := d.D[i].Key, d.D[i].Value + jKey, jValue := d.D[j].Key, d.D[j].Value + + d.D[i].Key, d.D[i].Value = jKey, jValue + d.D[j].Key, d.D[j].Value = iKey, iValue +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (d *Dict) Less(i, j int) bool { + return d.D[i].Key < d.D[j].Key +} + +// Get get data from key. +func (d *Dict) Get(key string) interface{} { + idx := d.indexOf(key) + if idx > -1 { + return d.D[idx].Value + } + + return nil +} + +// GetBytes get data from key. +func (d *Dict) GetBytes(key []byte) interface{} { + return d.Get(utils.UnsafeString(key)) +} + +// Set set new key. +func (d *Dict) Set(key string, value interface{}) { + idx := d.indexOf(key) + if idx > -1 { + kv := &d.D[idx] + kv.Value = value + } else { + d.append(key, value) + + if d.BinarySearch { + sort.Sort(d) + } + } +} + +// SetBytes set new key. +func (d *Dict) SetBytes(key []byte, value interface{}) { + d.Set(utils.UnsafeString(key), value) +} + +// Del delete key. +func (d *Dict) Del(key string) { + idx := d.indexOf(key) + if idx > -1 { + n := len(d.D) - 1 + d.Swap(idx, n) + d.D = d.D[:n] // Remove last position + } +} + +// DelBytes delete key. +func (d *Dict) DelBytes(key []byte) { + d.Del(utils.UnsafeString(key)) +} + +// Has check if key exists. +func (d *Dict) Has(key string) bool { + return d.indexOf(key) > -1 +} + +// HasBytes check if key exists. +func (d *Dict) HasBytes(key []byte) bool { + return d.Has(utils.UnsafeString(key)) +} + +// Reset reset dict. +func (d *Dict) Reset() { + d.D = d.D[:0] +} + +// Map convert to map. +func (d *Dict) Map(dst DictMap) { + for i := range d.D { + kv := &d.D[i] + + sd, ok := kv.Value.(*Dict) + if ok { + subDst := make(DictMap) + sd.Map(subDst) + dst[kv.Key] = subDst + } else { + dst[kv.Key] = kv.Value + } + } +} + +// Parse convert map to Dict. +func (d *Dict) Parse(src DictMap) { + d.Reset() + + for k, v := range src { + sv, ok := v.(map[string]interface{}) + if ok { + subDict := new(Dict) + subDict.Parse(sv) + d.append(k, subDict) + } else { + d.append(k, v) + } + } +} diff --git a/internal/dictpool/pool.go b/internal/dictpool/pool.go new file mode 100644 index 0000000000..6160a4ff65 --- /dev/null +++ b/internal/dictpool/pool.go @@ -0,0 +1,20 @@ +package dictpool + +import "sync" + +var defaultPool = sync.Pool{ + New: func() interface{} { + return new(Dict) + }, +} + +// AcquireDict acquire new dict. +func AcquireDict() *Dict { + return defaultPool.Get().(*Dict) // nolint:forcetypeassert +} + +// ReleaseDict release dict. +func ReleaseDict(d *Dict) { + d.Reset() + defaultPool.Put(d) +} diff --git a/internal/dictpool/types.go b/internal/dictpool/types.go new file mode 100644 index 0000000000..bfcd97796c --- /dev/null +++ b/internal/dictpool/types.go @@ -0,0 +1,25 @@ +package dictpool + +//go:generate msgp + +// KV struct so it storages key/value data. +type KV struct { + Key string + Value interface{} +} + +// Dict dictionary as slice with better performance. +type Dict struct { + // D slice of KV for storage the data + D []KV + + // Use binary search to the get an item. + // It's only useful on big heaps. + // + // WARNING: Increase searching performance on big heaps, + // but whe set new items could be slowier due to the sorting. + BinarySearch bool +} + +// DictMap dictionary as map. +type DictMap map[string]interface{} diff --git a/internal/dictpool/types_gen.go b/internal/dictpool/types_gen.go new file mode 100644 index 0000000000..e1213d3e10 --- /dev/null +++ b/internal/dictpool/types_gen.go @@ -0,0 +1,509 @@ +package dictpool + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "github.com/gofiber/fiber/v2/internal/msgp" +) + +// DecodeMsg implements msgp.Decodable +func (z *Dict) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "D": + var zb0002 uint32 + zb0002, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err, "D") + return + } + if cap(z.D) >= int(zb0002) { + z.D = (z.D)[:zb0002] + } else { + z.D = make([]KV, zb0002) + } + for za0001 := range z.D { + var zb0003 uint32 + zb0003, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + for zb0003 > 0 { + zb0003-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + switch msgp.UnsafeString(field) { + case "Key": + z.D[za0001].Key, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Key") + return + } + case "Value": + z.D[za0001].Value, err = dc.ReadIntf() + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Value") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + } + } + } + case "BinarySearch": + z.BinarySearch, err = dc.ReadBool() + if err != nil { + err = msgp.WrapError(err, "BinarySearch") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *Dict) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 2 + // write "D" + err = en.Append(0x82, 0xa1, 0x44) + if err != nil { + return + } + err = en.WriteArrayHeader(uint32(len(z.D))) + if err != nil { + err = msgp.WrapError(err, "D") + return + } + for za0001 := range z.D { + // map header, size 2 + // write "Key" + err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79) + if err != nil { + return + } + err = en.WriteString(z.D[za0001].Key) + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Key") + return + } + // write "Value" + err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65) + if err != nil { + return + } + err = en.WriteIntf(z.D[za0001].Value) + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Value") + return + } + } + // write "BinarySearch" + err = en.Append(0xac, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68) + if err != nil { + return + } + err = en.WriteBool(z.BinarySearch) + if err != nil { + err = msgp.WrapError(err, "BinarySearch") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *Dict) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "D" + o = append(o, 0x82, 0xa1, 0x44) + o = msgp.AppendArrayHeader(o, uint32(len(z.D))) + for za0001 := range z.D { + // map header, size 2 + // string "Key" + o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79) + o = msgp.AppendString(o, z.D[za0001].Key) + // string "Value" + o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65) + o, err = msgp.AppendIntf(o, z.D[za0001].Value) + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Value") + return + } + } + // string "BinarySearch" + o = append(o, 0xac, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68) + o = msgp.AppendBool(o, z.BinarySearch) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Dict) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "D": + var zb0002 uint32 + zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "D") + return + } + if cap(z.D) >= int(zb0002) { + z.D = (z.D)[:zb0002] + } else { + z.D = make([]KV, zb0002) + } + for za0001 := range z.D { + var zb0003 uint32 + zb0003, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + switch msgp.UnsafeString(field) { + case "Key": + z.D[za0001].Key, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Key") + return + } + case "Value": + z.D[za0001].Value, bts, err = msgp.ReadIntfBytes(bts) + if err != nil { + err = msgp.WrapError(err, "D", za0001, "Value") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err, "D", za0001) + return + } + } + } + } + case "BinarySearch": + z.BinarySearch, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "BinarySearch") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Dict) Msgsize() (s int) { + s = 1 + 2 + msgp.ArrayHeaderSize + for za0001 := range z.D { + s += 1 + 4 + msgp.StringPrefixSize + len(z.D[za0001].Key) + 6 + msgp.GuessSize(z.D[za0001].Value) + } + s += 13 + msgp.BoolSize + return +} + +// DecodeMsg implements msgp.Decodable +func (z *DictMap) DecodeMsg(dc *msgp.Reader) (err error) { + var zb0003 uint32 + zb0003, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + if (*z) == nil { + (*z) = make(DictMap, zb0003) + } else if len((*z)) > 0 { + for key := range *z { + delete((*z), key) + } + } + for zb0003 > 0 { + zb0003-- + var zb0001 string + var zb0002 interface{} + zb0001, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err) + return + } + zb0002, err = dc.ReadIntf() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + (*z)[zb0001] = zb0002 + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z DictMap) EncodeMsg(en *msgp.Writer) (err error) { + err = en.WriteMapHeader(uint32(len(z))) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0004, zb0005 := range z { + err = en.WriteString(zb0004) + if err != nil { + err = msgp.WrapError(err) + return + } + err = en.WriteIntf(zb0005) + if err != nil { + err = msgp.WrapError(err, zb0004) + return + } + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z DictMap) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendMapHeader(o, uint32(len(z))) + for zb0004, zb0005 := range z { + o = msgp.AppendString(o, zb0004) + o, err = msgp.AppendIntf(o, zb0005) + if err != nil { + err = msgp.WrapError(err, zb0004) + return + } + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *DictMap) UnmarshalMsg(bts []byte) (o []byte, err error) { + var zb0003 uint32 + zb0003, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if (*z) == nil { + (*z) = make(DictMap, zb0003) + } else if len((*z)) > 0 { + for key := range *z { + delete((*z), key) + } + } + for zb0003 > 0 { + var zb0001 string + var zb0002 interface{} + zb0003-- + zb0001, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + zb0002, bts, err = msgp.ReadIntfBytes(bts) + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + (*z)[zb0001] = zb0002 + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z DictMap) Msgsize() (s int) { + s = msgp.MapHeaderSize + if z != nil { + for zb0004, zb0005 := range z { + _ = zb0005 + s += msgp.StringPrefixSize + len(zb0004) + msgp.GuessSize(zb0005) + } + } + return +} + +// DecodeMsg implements msgp.Decodable +func (z *KV) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "Key": + z.Key, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Key") + return + } + case "Value": + z.Value, err = dc.ReadIntf() + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z KV) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 2 + // write "Key" + err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79) + if err != nil { + return + } + err = en.WriteString(z.Key) + if err != nil { + err = msgp.WrapError(err, "Key") + return + } + // write "Value" + err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65) + if err != nil { + return + } + err = en.WriteIntf(z.Value) + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z KV) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "Key" + o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79) + o = msgp.AppendString(o, z.Key) + // string "Value" + o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65) + o, err = msgp.AppendIntf(o, z.Value) + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *KV) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "Key": + z.Key, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Key") + return + } + case "Value": + z.Value, bts, err = msgp.ReadIntfBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z KV) Msgsize() (s int) { + s = 1 + 4 + msgp.StringPrefixSize + len(z.Key) + 6 + msgp.GuessSize(z.Value) + return +}