From 596c7776a0c69c4ee9033f59e0a4c2a7cd951b3b Mon Sep 17 00:00:00 2001 From: rock Date: Sun, 3 Jan 2021 13:23:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=A5=20add=20function=20to=20overid?= =?UTF-8?q?e=20form=20decoder=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔥 Feature : SetBodyParserDecoder map all option to form decoder, use with BodyParserConfig, BodyParserType 🚨 Test : Test_Ctx_BodyParser_WithSetBodyParserDecoder --- ctx.go | 31 ++++++++++++++++++++++++++++++ ctx_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/ctx.go b/ctx.go index 961e0b7efc..1721affcac 100644 --- a/ctx.go +++ b/ctx.go @@ -77,6 +77,21 @@ type Views interface { Render(io.Writer, string, interface{}, ...string) error } +// BodyParserType require two element, type and converter for register. +// Use BodyParserType with BodyParser for parsing custom type in form data. +type BodyParserType struct { + Customtype interface{} + Converter func(string) reflect.Value +} + +// BodyParserConfig form decoder config for SetBodyParserDecoder +type BodyParserConfig struct { + IgnoreUnknownKeys bool + SetAliasTag string + BodyParserType []BodyParserType + ZeroEmpty bool +} + // AcquireCtx retrieves a new Ctx from the pool. func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx { c := app.pool.Get().(*Ctx) @@ -244,6 +259,22 @@ var decoderPool = &sync.Pool{New: func() interface{} { return decoder }} +// SetBodyParserDecoder allow globally change the option of form decoder, update decoderPool +func SetBodyParserDecoder(bodyParserConfig BodyParserConfig) { + decoderPool = &sync.Pool{New: func() interface{} { + var decoder = schema.NewDecoder() + decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) + if bodyParserConfig.SetAliasTag != "" { + decoder.SetAliasTag(bodyParserConfig.SetAliasTag) + } + for _, v := range bodyParserConfig.BodyParserType { + decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) + } + decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) + return decoder + }} +} + // BodyParser binds the request body to a struct. // It supports decoding the following content types based on the Content-Type header: // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data diff --git a/ctx_test.go b/ctx_test.go index cd22f51b4c..f37175f9b6 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -339,6 +339,60 @@ func Test_Ctx_BodyParser(t *testing.T) { testDecodeParserError(MIMEMultipartForm+`;boundary="b"`, "--b") } +// go test -run Test_Ctx_BodyParser_WithSetBodyParserDecoder +func Test_Ctx_BodyParser_WithSetBodyParserDecoder(t *testing.T) { + type CustomTime time.Time + + var timeConverter = func(value string) reflect.Value { + if v, err := time.Parse("2006-01-02", value); err == nil { + return reflect.ValueOf(v) + } + return reflect.Value{} + } + + customTime := BodyParserType{ + Customtype: CustomTime{}, + Converter: timeConverter, + } + + SetBodyParserDecoder(BodyParserConfig{ + IgnoreUnknownKeys: true, + BodyParserType: []BodyParserType{customTime}, + ZeroEmpty: true, + SetAliasTag: "form", + }) + + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + + type Demo struct { + Date CustomTime `form:"date"` + Title string `form:"title"` + Body string `form:"body"` + } + + testDecodeParser := func(contentType, body string) { + c.Request().Header.SetContentType(contentType) + c.Request().SetBody([]byte(body)) + c.Request().Header.SetContentLength(len(body)) + d := Demo{ + Title: "Existing title", + Body: "Existing Body", + } + utils.AssertEqual(t, nil, c.BodyParser(&d)) + date := fmt.Sprintf("%v", d.Date) + fmt.Println(date, d.Title, d.Body) + utils.AssertEqual(t, "{0 63743587200 }", date) + utils.AssertEqual(t, "", d.Title) + utils.AssertEqual(t, "New Body", d.Body) + } + + testDecodeParser(MIMEApplicationForm, "date=2020-12-15&title=&body=New Body") + testDecodeParser(MIMEMultipartForm+`; boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"date\"\r\n\r\n2020-12-15\r\n--b\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n--b\r\nContent-Disposition: form-data; name=\"body\"\r\n\r\nNew Body\r\n--b--") +} + // go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_JSON -benchmem -count=4 func Benchmark_Ctx_BodyParser_JSON(b *testing.B) { app := New() From e7e9dca1c6d28e76b7d5c994b4c7d9b5975621f4 Mon Sep 17 00:00:00 2001 From: Rock Date: Thu, 30 Sep 2021 23:58:12 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A5=20Use=20decoder=20builder=20fu?= =?UTF-8?q?nction=20with=20default=20setting=20on=20init=20decoderPool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ctx.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ctx.go b/ctx.go index 1721affcac..2900135324 100644 --- a/ctx.go +++ b/ctx.go @@ -254,27 +254,32 @@ func (c *Ctx) Body() []byte { // decoderPool helps to improve BodyParser's and QueryParser's performance var decoderPool = &sync.Pool{New: func() interface{} { - var decoder = schema.NewDecoder() - decoder.IgnoreUnknownKeys(true) - return decoder + return decoderBuilder(BodyParserConfig{ + IgnoreUnknownKeys: true, + ZeroEmpty: true, + }) }} // SetBodyParserDecoder allow globally change the option of form decoder, update decoderPool func SetBodyParserDecoder(bodyParserConfig BodyParserConfig) { decoderPool = &sync.Pool{New: func() interface{} { - var decoder = schema.NewDecoder() - decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) - if bodyParserConfig.SetAliasTag != "" { - decoder.SetAliasTag(bodyParserConfig.SetAliasTag) - } - for _, v := range bodyParserConfig.BodyParserType { - decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) - } - decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) - return decoder + return decoderBuilder(bodyParserConfig) }} } +func decoderBuilder(bodyParserConfig BodyParserConfig) interface{} { + var decoder = schema.NewDecoder() + decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) + if bodyParserConfig.SetAliasTag != "" { + decoder.SetAliasTag(bodyParserConfig.SetAliasTag) + } + for _, v := range bodyParserConfig.BodyParserType { + decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) + } + decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) + return decoder +} + // BodyParser binds the request body to a struct. // It supports decoding the following content types based on the Content-Type header: // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data