diff --git a/net/ghttp/ghttp_request.go b/net/ghttp/ghttp_request.go index 73801995b3d..fe01d590bbe 100644 --- a/net/ghttp/ghttp_request.go +++ b/net/ghttp/ghttp_request.go @@ -178,6 +178,22 @@ func (r *Request) IsAjaxRequest() bool { return strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") } +// IsMultiPartRequest checks and returns whether current request is an MultiPart request. +func (r *Request) IsMultiPartRequest() bool { + if contentType := r.Header.Get("Content-Type"); contentType != "" { + return gstr.Contains(contentType, "multipart/") + } + return false +} + +// IsFormRequest checks and returns whether current request is an Form request. +func (r *Request) IsFormRequest() bool { + if contentType := r.Header.Get("Content-Type"); contentType != "" { + return gstr.Contains(contentType, "form") + } + return false +} + // GetClientIp returns the client ip of this request without port. // Note that this ip address might be modified by client header. func (r *Request) GetClientIp() string { diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 20f67d2ef53..c62eb30e6b8 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -269,27 +269,23 @@ func (r *Request) parseForm() { return } if contentType := r.Header.Get("Content-Type"); contentType != "" { - var ( - err error - repeatableRead = true - ) - if gstr.Contains(contentType, "multipart/") { + var err error + if !r.IsMultiPartRequest() { // To avoid big memory consuming. // The `multipart/` type form always contains binary data, which is not necessary read twice. - repeatableRead = false + r.MakeBodyRepeatableRead(true) + } + if r.IsMultiPartRequest() { // multipart/form-data, multipart/mixed if err = r.ParseMultipartForm(r.Server.config.FormParsingMemory); err != nil { panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "r.ParseMultipartForm failed")) } - } else if gstr.Contains(contentType, "form") { + } else if r.IsFormRequest() { // application/x-www-form-urlencoded if err = r.Request.ParseForm(); err != nil { panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "r.Request.ParseForm failed")) } } - if repeatableRead { - r.MakeBodyRepeatableRead(true) - } if len(r.PostForm) > 0 { // Parse the form data using united parsing way. params := "" diff --git a/net/ghttp/ghttp_z_unit_feature_request_struct_test.go b/net/ghttp/ghttp_z_unit_feature_request_struct_test.go index af846b75da1..a54e73ae434 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_struct_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_struct_test.go @@ -115,6 +115,42 @@ func Test_Params_ParseForm(t *testing.T) { }) } +// https://github.com/gogf/gf/pull/4143 +func Test_Params_ParseForm_FixMakeBodyRepeatableRead(t *testing.T) { + type User struct { + Id int + Name string + } + s := g.Server(guid.S()) + s.BindHandler("/parse-form", func(r *ghttp.Request) { + var user *User + if err := r.ParseForm(&user); err != nil { + r.Response.WriteExit(err) + } + hasBody := len(r.GetBody()) > 0 + r.Response.WriteExit(hasBody) + }) + 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", s.GetListenedPort())) + t.Assert(c.GetContent(ctx, "/parse-form"), `false`) + t.Assert(c.GetContent(ctx, "/parse-form", g.Map{ + "id": 1, + "name": "john", + }), false) + t.Assert(c.PostContent(ctx, "/parse-form"), `false`) + t.Assert(c.PostContent(ctx, "/parse-form", g.Map{ + "id": 1, + "name": "john", + }), true) + }) +} + func Test_Params_ComplexJsonStruct(t *testing.T) { type ItemEnv struct { Type string