diff --git a/.example/net/ghttp/server/request/json-xml/test.go b/.example/net/ghttp/server/request/json-xml/test.go new file mode 100644 index 000000000..5e3aed050 --- /dev/null +++ b/.example/net/ghttp/server/request/json-xml/test.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/util/gvalid" +) + +type RegisterReq struct { + Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"` + Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"` + Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"` +} + +type RegisterRes struct { + Code int `json:"code"` + Error string `json:"error"` + Data interface{} `json:"data"` +} + +func main() { + s := g.Server() + s.BindHandler("/register", func(r *ghttp.Request) { + var req *RegisterReq + //fmt.Println(r.GetBody()) + if err := r.Parse(&req); err != nil { + // Validation error. + if v, ok := err.(*gvalid.Error); ok { + r.Response.WriteJsonExit(RegisterRes{ + Code: 1, + Error: v.FirstString(), + }) + } + // Other error. + r.Response.WriteJsonExit(RegisterRes{ + Code: 1, + Error: err.Error(), + }) + } + // ... + r.Response.WriteJsonExit(RegisterRes{ + Data: req, + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/request/struct/parse2.go b/.example/net/ghttp/server/request/struct/parse2.go index 66e4d093e..fa72da655 100644 --- a/.example/net/ghttp/server/request/struct/parse2.go +++ b/.example/net/ghttp/server/request/struct/parse2.go @@ -27,6 +27,7 @@ func main() { Error: err.Error(), }) } + // ... r.Response.WriteJsonExit(RegisterRes{ Data: req, }) diff --git a/.example/net/ghttp/server/request/validation/validation1.go b/.example/net/ghttp/server/request/validation/validation1.go new file mode 100644 index 000000000..51a3c8346 --- /dev/null +++ b/.example/net/ghttp/server/request/validation/validation1.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +type RegisterReq struct { + Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"` + Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"` + Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"` +} + +type RegisterRes struct { + Code int `json:"code"` + Error string `json:"error"` + Data interface{} `json:"data"` +} + +func main() { + s := g.Server() + s.BindHandler("/register", func(r *ghttp.Request) { + var req *RegisterReq + if err := r.Parse(&req); err != nil { + r.Response.WriteJsonExit(RegisterRes{ + Code: 1, + Error: err.Error(), + }) + } + // ... + r.Response.WriteJsonExit(RegisterRes{ + Data: req, + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/request/validation/validation2.go b/.example/net/ghttp/server/request/validation/validation2.go new file mode 100644 index 000000000..ccee1e3f9 --- /dev/null +++ b/.example/net/ghttp/server/request/validation/validation2.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/util/gvalid" +) + +type RegisterReq struct { + Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"` + Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"` + Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|两次密码不一致"` +} + +type RegisterRes struct { + Code int `json:"code"` + Error string `json:"error"` + Data interface{} `json:"data"` +} + +func main() { + s := g.Server() + s.BindHandler("/register", func(r *ghttp.Request) { + var req *RegisterReq + if err := r.Parse(&req); err != nil { + // Validation error. + if v, ok := err.(*gvalid.Error); ok { + r.Response.WriteJsonExit(RegisterRes{ + Code: 1, + Error: v.FirstString(), + }) + } + // Other error. + r.Response.WriteJsonExit(RegisterRes{ + Code: 1, + Error: err.Error(), + }) + } + // ... + r.Response.WriteJsonExit(RegisterRes{ + Data: req, + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/encoding/gxml/gxml_test.go b/encoding/gxml/gxml_test.go index 5fb7a4ffc..86d602f2a 100644 --- a/encoding/gxml/gxml_test.go +++ b/encoding/gxml/gxml_test.go @@ -80,7 +80,7 @@ func Test_XmlToJson(t *testing.T) { } } -func Test_Decode(t *testing.T) { +func Test_Decode1(t *testing.T) { for _, v := range testData { srcXml, dstXml := buildXml(v.otherEncoding, v.utf8) if len(srcXml) == 0 && len(dstXml) == 0 { @@ -106,6 +106,32 @@ func Test_Decode(t *testing.T) { } } +func Test_Decode2(t *testing.T) { + gtest.Case(t, func() { + content := ` +johngcn123456123456 +` + m, err := gxml.Decode([]byte(content)) + gtest.Assert(err, nil) + gtest.Assert(m["doc"].(map[string]interface{})["username"], "johngcn") + gtest.Assert(m["doc"].(map[string]interface{})["password1"], "123456") + gtest.Assert(m["doc"].(map[string]interface{})["password2"], "123456") + }) +} + +func Test_DecodeWitoutRoot(t *testing.T) { + gtest.Case(t, func() { + content := ` +johngcn123456123456 +` + m, err := gxml.DecodeWithoutRoot([]byte(content)) + gtest.Assert(err, nil) + gtest.Assert(m["username"], "johngcn") + gtest.Assert(m["password1"], "123456") + gtest.Assert(m["password2"], "123456") + }) +} + func Test_Encode(t *testing.T) { m := make(map[string]interface{}) v := map[string]interface{}{ diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 4acf684c9..c56511728 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -13,11 +13,13 @@ import ( "github.com/gogf/gf/encoding/gjson" "github.com/gogf/gf/encoding/gurl" "github.com/gogf/gf/encoding/gxml" + "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gvalid" "io/ioutil" "mime/multipart" + "strings" ) var ( @@ -210,6 +212,8 @@ func (r *Request) parseBody() { // parseForm parses the request form for HTTP method PUT, POST, PATCH. // The form data is pared into r.formMap. +// +// Note that if the form was parsed firstly, the request body would be cleared and empty. func (r *Request) parseForm() { if r.parsedForm { return @@ -232,6 +236,15 @@ func (r *Request) parseForm() { // Re-parse the form data using united parsing way. params := "" for name, values := range r.PostForm { + // Invalid parameter name. + if !gregex.IsMatchString(`^[\w\[\]]+$`, name) { + if len(r.PostForm) == 1 { + // It might be JSON/XML content. + r.bodyContent = gconv.UnsafeStrToBytes(name + strings.Join(values, " ")) + } + params = "" + break + } if len(values) == 1 { if len(params) > 0 { params += "&" @@ -257,7 +270,8 @@ func (r *Request) parseForm() { if r.formMap, err = gstr.Parse(params); err != nil { panic(err) } - } else { + } + if r.formMap == nil { r.parseBody() if len(r.bodyMap) > 0 { r.formMap = r.bodyMap diff --git a/text/gstr/gstr_parse.go b/text/gstr/gstr_parse.go index 14c88c39d..c44c2cbe5 100644 --- a/text/gstr/gstr_parse.go +++ b/text/gstr/gstr_parse.go @@ -24,6 +24,9 @@ import ( // a .[[b=c -> map[a___[b:c] // func Parse(s string) (result map[string]interface{}, err error) { + if s == "" { + return nil, nil + } result = make(map[string]interface{}) parts := strings.Split(s, "&") for _, part := range parts { diff --git a/util/gvalid/gvalid_check_struct.go b/util/gvalid/gvalid_check_struct.go index ddbca85b2..6bc7e7b5a 100644 --- a/util/gvalid/gvalid_check_struct.go +++ b/util/gvalid/gvalid_check_struct.go @@ -73,53 +73,51 @@ func CheckStruct(object interface{}, rules interface{}, msgs ...CustomMsg) *Erro params[field.Name()] = field.Value() } // 首先, 按照属性循环一遍将struct的属性、数值、tag解析 - for nameOrTag, field := range structs.MapField(object, structTagPriority, true) { + // It here must use structs.TagFields not structs.MapField to ensure error sequence. + for _, field := range structs.TagFields(object, structTagPriority, true) { fieldName := field.Name() - // MapField返回map[name/tag]*Field,当nameOrTag != fieldName时,nameOrTag为tag - if nameOrTag != fieldName { - // sequence tag == struct tag, 这里的name为别名 - name, rule, msg := parseSequenceTag(nameOrTag) - if len(name) == 0 { - name = fieldName + // sequence tag == struct tag, 这里的name为别名 + name, rule, msg := parseSequenceTag(field.Tag) + if len(name) == 0 { + name = fieldName + } else { + fieldAliases[fieldName] = name + } + // params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用 + if _, ok := params[name]; !ok { + params[name] = field.Value() + } + // 校验规则 + if _, ok := checkRules[name]; !ok { + if _, ok := checkRules[fieldName]; ok { + // tag中存在别名,且rules传入的参数中使用了属性命名时,进行规则替换,并删除该属性的规则 + checkRules[name] = checkRules[fieldName] + delete(checkRules, fieldName) } else { - fieldAliases[fieldName] = name + checkRules[name] = rule } - // params参数使用别名**扩容**(而不仅仅使用别名),仅用于验证使用 - if _, ok := params[name]; !ok { - params[name] = field.Value() - } - // 校验规则 - if _, ok := checkRules[name]; !ok { - if _, ok := checkRules[fieldName]; ok { - // tag中存在别名,且rules传入的参数中使用了属性命名时,进行规则替换,并删除该属性的规则 - checkRules[name] = checkRules[fieldName] - delete(checkRules, fieldName) - } else { - checkRules[name] = rule + errorRules = append(errorRules, name+"@"+rule) + } else { + // 传递的rules规则会覆盖struct tag的规则 + continue + } + // 错误提示 + if len(msg) > 0 { + ruleArray := strings.Split(rule, "|") + msgArray := strings.Split(msg, "|") + for k, v := range ruleArray { + // 如果msg条数比rule少,那么多余的rule使用默认的错误信息 + if len(msgArray) <= k { + continue } - errorRules = append(errorRules, name+"@"+rule) - } else { - // 传递的rules规则会覆盖struct tag的规则 - continue - } - // 错误提示 - if len(msg) > 0 { - ruleArray := strings.Split(rule, "|") - msgArray := strings.Split(msg, "|") - for k, v := range ruleArray { - // 如果msg条数比rule少,那么多余的rule使用默认的错误信息 - if len(msgArray) <= k { - continue - } - if len(msgArray[k]) == 0 { - continue - } - array := strings.Split(v, ":") - if _, ok := customMsgs[name]; !ok { - customMsgs[name] = make(map[string]string) - } - customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) + if len(msgArray[k]) == 0 { + continue } + array := strings.Split(v, ":") + if _, ok := customMsgs[name]; !ok { + customMsgs[name] = make(map[string]string) + } + customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) } } }