From 9c3b978b5082d0ed96cc5e93b1a053000232b302 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 22 Oct 2020 15:16:31 +0800 Subject: [PATCH] improve package ghttp and internal/structs --- internal/structs/structs_map.go | 21 +++++- internal/structs/structs_tag.go | 15 +++++ internal/structs/structs_test.go | 17 +++++ net/ghttp/ghttp_request_param.go | 57 +++++++++++++--- net/ghttp/ghttp_request_param_query.go | 8 ++- net/ghttp/ghttp_unit_param_struct_test.go | 81 +++++++++++++++++++++-- net/ghttp/ghttp_unit_param_test.go | 14 ++-- 7 files changed, 184 insertions(+), 29 deletions(-) diff --git a/internal/structs/structs_map.go b/internal/structs/structs_map.go index da758b94d..a029aa647 100644 --- a/internal/structs/structs_map.go +++ b/internal/structs/structs_map.go @@ -22,6 +22,21 @@ import ( // // Note that it only retrieves the exported attributes with first letter up-case from struct. func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field { + // If points to an invalid address, for example a nil variable, + // it here creates an empty struct using reflect feature. + var ( + tempValue reflect.Value + pointerValue = reflect.ValueOf(pointer) + ) + for pointerValue.Kind() == reflect.Ptr { + tempValue = pointerValue.Elem() + if !tempValue.IsValid() { + pointer = reflect.New(pointerValue.Type().Elem()).Elem() + break + } else { + pointerValue = tempValue + } + } var ( fields []*structs.Field fieldMap = make(map[string]*Field) @@ -59,8 +74,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string } } if recursive { - rv := reflect.ValueOf(field.Value()) - kind := rv.Kind() + var ( + rv = reflect.ValueOf(field.Value()) + kind = rv.Kind() + ) if kind == reflect.Ptr { rv = rv.Elem() kind = rv.Kind() diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index 8e2375f3b..544068d3f 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -27,6 +27,21 @@ func TagFields(pointer interface{}, priority []string, recursive bool) []*Field // tag internally. // The parameter should be type of struct/*struct. func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field { + // If points to an invalid address, for example a nil variable, + // it here creates an empty struct using reflect feature. + var ( + tempValue reflect.Value + pointerValue = reflect.ValueOf(pointer) + ) + for pointerValue.Kind() == reflect.Ptr { + tempValue = pointerValue.Elem() + if !tempValue.IsValid() { + pointer = reflect.New(pointerValue.Type().Elem()).Elem() + break + } else { + pointerValue = tempValue + } + } var fields []*structs.Field if v, ok := pointer.(reflect.Value); ok { fields = structs.Fields(v.Interface()) diff --git a/internal/structs/structs_test.go b/internal/structs/structs_test.go index 1f5074d93..ae5c2dd08 100644 --- a/internal/structs/structs_test.go +++ b/internal/structs/structs_test.go @@ -71,3 +71,20 @@ func Test_Basic(t *testing.T) { t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"}) }) } + +func Test_StructOfNilPointer(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user *User + t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) + + t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"}) + }) +} diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 8ad00ec0b..83b351b3d 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -25,6 +25,12 @@ import ( "strings" ) +const ( + parseTypeRequest = 0 + parseTypeQuery = 1 + parseTypeForm = 2 +) + var ( // xmlHeaderBytes is the most common XML format header. xmlHeaderBytes = []byte(" can be type of: *struct/**struct/*[]struct/*[]*struct. // // It supports single and multiple struct convertion: -// 1. Single struct, post content like: {"id":1, "name":"john"} +// 1. Single struct, post content like: {"id":1, "name":"john"} or ?id=1&name=john // 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}] // // TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages. func (r *Request) Parse(pointer interface{}) error { + return r.doParse(pointer, parseTypeRequest) +} + +// ParseQuery performs like function Parse, but only parses the query parameters. +func (r *Request) ParseQuery(pointer interface{}) error { + return r.doParse(pointer, parseTypeQuery) +} + +// ParseForm performs like function Parse, but only parses the form parameters or the body content. +func (r *Request) ParseForm(pointer interface{}) error { + return r.doParse(pointer, parseTypeForm) +} + +// doParse parses the request data to struct/structs according to request type. +func (r *Request) doParse(pointer interface{}, requestType int) error { var ( reflectVal1 = reflect.ValueOf(pointer) reflectKind1 = reflectVal1.Kind() @@ -58,18 +79,31 @@ func (r *Request) Parse(pointer interface{}) error { ) switch reflectKind2 { // Single struct, post content like: - // {"id":1, "name":"john"} + // 1. {"id":1, "name":"john"} + // 2. ?id=1&name=john case reflect.Ptr, reflect.Struct: - // Conversion. - if err := r.GetStruct(pointer); err != nil { - return err + // Converting. + switch requestType { + case parseTypeQuery: + if err := r.GetQueryStruct(pointer); err != nil { + return err + } + case parseTypeForm: + if err := r.GetFormStruct(pointer); err != nil { + return err + } + default: + if err := r.GetStruct(pointer); err != nil { + return err + } } + // Validation. if err := gvalid.CheckStruct(pointer, nil); err != nil { return err } - // Multiple struct, post content like: + // Multiple struct, it only supports JSON type post content like: // [{"id":1, "name":"john"}, {"id":, "name":"smith"}] case reflect.Array, reflect.Slice: // If struct slice conversion, it might post JSON/XML content, @@ -371,11 +405,14 @@ func (r *Request) parseForm() { } } } - if r.formMap == nil { + } + // It parses the request body without checking the Content-Type. + if r.formMap == nil { + if r.Method != "GET" { r.parseBody() - if len(r.bodyMap) > 0 { - r.formMap = r.bodyMap - } + } + if len(r.bodyMap) > 0 { + r.formMap = r.bodyMap } } } diff --git a/net/ghttp/ghttp_request_param_query.go b/net/ghttp/ghttp_request_param_query.go index a5355561f..1266a453b 100644 --- a/net/ghttp/ghttp_request_param_query.go +++ b/net/ghttp/ghttp_request_param_query.go @@ -34,7 +34,9 @@ func (r *Request) GetQuery(key string, def ...interface{}) interface{} { return v } } - r.parseBody() + if r.Method == "GET" { + r.parseBody() + } if len(r.bodyMap) > 0 { if v, ok := r.bodyMap[key]; ok { return v @@ -118,7 +120,9 @@ func (r *Request) GetQueryInterfaces(key string, def ...interface{}) []interface // in order of priority: query > body. func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interface{} { r.parseQuery() - r.parseBody() + if r.Method == "GET" { + r.parseBody() + } var m map[string]interface{} if len(kvMap) > 0 && kvMap[0] != nil { if len(r.queryMap) == 0 && len(r.bodyMap) == 0 { diff --git a/net/ghttp/ghttp_unit_param_struct_test.go b/net/ghttp/ghttp_unit_param_struct_test.go index 4090fb0bf..0738ce50c 100644 --- a/net/ghttp/ghttp_unit_param_struct_test.go +++ b/net/ghttp/ghttp_unit_param_struct_test.go @@ -27,13 +27,11 @@ func Test_Params_Parse(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) s.BindHandler("/parse", func(r *ghttp.Request) { - if m := r.GetMap(); len(m) > 0 { - var user *User - if err := r.Parse(&user); err != nil { - r.Response.WriteExit(err) - } - r.Response.WriteExit(user.Map["id"], user.Map["score"]) + var user *User + if err := r.Parse(&user); err != nil { + r.Response.WriteExit(err) } + r.Response.WriteExit(user.Map["id"], user.Map["score"]) }) s.SetPort(p) s.SetDumpRouterMap(false) @@ -48,7 +46,76 @@ func Test_Params_Parse(t *testing.T) { }) } -func Test_Params_Parse2(t *testing.T) { +func Test_Params_ParseQuery(t *testing.T) { + type User struct { + Id int + Name string + } + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/parse-query", func(r *ghttp.Request) { + var user *User + if err := r.ParseQuery(&user); err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(user.Id, user.Name) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + t.Assert(c.GetContent("/parse-query"), `0`) + t.Assert(c.GetContent("/parse-query?id=1&name=john"), `1john`) + t.Assert(c.PostContent("/parse-query"), `0`) + t.Assert(c.PostContent("/parse-query", g.Map{ + "id": 1, + "name": "john", + }), `0`) + }) +} + +func Test_Params_ParseForm(t *testing.T) { + type User struct { + Id int + Name string + } + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/parse-form", func(r *ghttp.Request) { + var user *User + if err := r.ParseForm(&user); err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(user.Id, user.Name) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + t.Assert(c.GetContent("/parse-form"), `0`) + t.Assert(c.GetContent("/parse-form", g.Map{ + "id": 1, + "name": "john", + }), 0) + t.Assert(c.PostContent("/parse-form"), `0`) + t.Assert(c.PostContent("/parse-form", g.Map{ + "id": 1, + "name": "john", + }), `1john`) + }) +} + +func Test_Params_ComplexJsonStruct(t *testing.T) { type ItemEnv struct { Type string Key string diff --git a/net/ghttp/ghttp_unit_param_test.go b/net/ghttp/ghttp_unit_param_test.go index 8e3cb79cf..d601e5ed6 100644 --- a/net/ghttp/ghttp_unit_param_test.go +++ b/net/ghttp/ghttp_unit_param_test.go @@ -433,10 +433,10 @@ func Test_Params_SupportChars(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) s.BindHandler("/form-value", func(r *ghttp.Request) { - r.Response.Write(r.GetQuery("test-value")) + r.Response.Write(r.GetForm("test-value")) }) s.BindHandler("/form-array", func(r *ghttp.Request) { - r.Response.Write(r.GetQuery("test-array")) + r.Response.Write(r.GetForm("test-array")) }) s.SetPort(p) s.SetDumpRouterMap(false) @@ -445,12 +445,10 @@ func Test_Params_SupportChars(t *testing.T) { time.Sleep(100 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - prefix := fmt.Sprintf("http://127.0.0.1:%d", p) - client := ghttp.NewClient() - client.SetPrefix(prefix) - - t.Assert(client.PostContent("/form-value", "test-value=100"), "100") - t.Assert(client.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`) + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + t.Assert(c.PostContent("/form-value", "test-value=100"), "100") + t.Assert(c.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`) }) }