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])
}
}
}