diff --git a/net/ghttp/ghttp.go b/net/ghttp/ghttp.go index 7de19a2e789..7359679c177 100644 --- a/net/ghttp/ghttp.go +++ b/net/ghttp/ghttp.go @@ -23,6 +23,7 @@ import ( "github.com/gogf/gf/v2/os/gcache" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/util/gtag" ) @@ -74,9 +75,11 @@ type ( // handlerFuncInfo contains the HandlerFunc address and its reflection type. handlerFuncInfo struct { - Func HandlerFunc // Handler function address. - Type reflect.Type // Reflect type information for current handler, which is used for extensions of the handler feature. - Value reflect.Value // Reflect value information for current handler, which is used for extensions of the handler feature. + Func HandlerFunc // Handler function address. + Type reflect.Type // Reflect type information for current handler, which is used for extensions of the handler feature. + Value reflect.Value // Reflect value information for current handler, which is used for extensions of the handler feature. + IsStrictRoute bool // Whether strict route matching is enabled. + ReqStructFields []gstructs.Field // Request struct fields. } // HandlerItem is the registered handler for route handling, diff --git a/net/ghttp/ghttp_request_middleware.go b/net/ghttp/ghttp_request_middleware.go index dc803132ad3..0d9ba3ec1ec 100644 --- a/net/ghttp/ghttp_request_middleware.go +++ b/net/ghttp/ghttp_request_middleware.go @@ -9,7 +9,6 @@ package ghttp import ( "context" "net/http" - "reflect" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" @@ -128,45 +127,6 @@ func (m *middleware) Next() { func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) { niceCallFunc(func() { - if funcInfo.Func != nil { - funcInfo.Func(m.request) - } else { - var inputValues = []reflect.Value{ - reflect.ValueOf(m.request.Context()), - } - if funcInfo.Type.NumIn() == 2 { - var inputObject reflect.Value - if funcInfo.Type.In(1).Kind() == reflect.Ptr { - inputObject = reflect.New(funcInfo.Type.In(1).Elem()) - m.request.error = m.request.Parse(inputObject.Interface()) - } else { - inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem() - m.request.error = m.request.Parse(inputObject.Addr().Interface()) - } - if m.request.error != nil { - return - } - inputValues = append(inputValues, inputObject) - } - - // Call handler with dynamic created parameter values. - results := funcInfo.Value.Call(inputValues) - switch len(results) { - case 1: - if !results[0].IsNil() { - if err, ok := results[0].Interface().(error); ok { - m.request.error = err - } - } - - case 2: - m.request.handlerResponse = results[0].Interface() - if !results[1].IsNil() { - if err, ok := results[1].Interface().(error); ok { - m.request.error = err - } - } - } - } + funcInfo.Func(m.request) }) } diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index a7eb7740ebb..76a4af2e85f 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -12,7 +12,6 @@ import ( "github.com/gogf/gf/v2/net/goai" "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/util/gconv" - "github.com/gogf/gf/v2/util/gtag" "github.com/gogf/gf/v2/util/gutil" ) @@ -192,7 +191,28 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string] // mergeDefaultStructValue merges the request parameters with default values from struct tag definition. func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error { - // TODO: https://github.com/gogf/gf/pull/2450 + fields := r.serveHandler.Handler.Info.ReqStructFields + if len(fields) > 0 { + var ( + foundKey string + foundValue interface{} + ) + for _, field := range fields { + if tagValue := field.TagDefault(); tagValue != "" { + foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name()) + if foundKey == "" { + data[field.Name()] = tagValue + } else { + if empty.IsEmpty(foundValue) { + data[foundKey] = tagValue + } + } + } + } + return nil + } + + // provide non strict routing tagFields, err := gstructs.TagFields(pointer, defaultValueTags) if err != nil { return err @@ -213,17 +233,14 @@ func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer i } } } + return nil } // mergeInTagStructValue merges the request parameters with header or cookie values from struct `in` tag definition. func (r *Request) mergeInTagStructValue(data map[string]interface{}, pointer interface{}) error { - // TODO: https://github.com/gogf/gf/pull/2450 - tagFields, err := gstructs.TagFields(pointer, []string{gtag.In}) - if err != nil { - return err - } - if len(tagFields) > 0 { + fields := r.serveHandler.Handler.Info.ReqStructFields + if len(fields) > 0 { var ( foundKey string foundValue interface{} @@ -241,29 +258,31 @@ func (r *Request) mergeInTagStructValue(data map[string]interface{}, pointer int cookieMap[cookie.Name] = cookie.Value } - for _, field := range tagFields { - switch field.TagValue { - case goai.ParameterInHeader: - foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.Name()) - if foundHeaderKey != "" { - foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey) - if foundKey == "" { - data[field.Name()] = foundHeaderValue - } else { - if empty.IsEmpty(foundValue) { - data[foundKey] = foundHeaderValue + for _, field := range fields { + if tagValue := field.TagIn(); tagValue != "" { + switch tagValue { + case goai.ParameterInHeader: + foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.Name()) + if foundHeaderKey != "" { + foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey) + if foundKey == "" { + data[field.Name()] = foundHeaderValue + } else { + if empty.IsEmpty(foundValue) { + data[foundKey] = foundHeaderValue + } } } - } - case goai.ParameterInCookie: - foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.Name()) - if foundCookieKey != "" { - foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey) - if foundKey == "" { - data[field.Name()] = foundCookieValue - } else { - if empty.IsEmpty(foundValue) { - data[foundKey] = foundCookieValue + case goai.ParameterInCookie: + foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.Name()) + if foundCookieKey != "" { + foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey) + if foundKey == "" { + data[field.Name()] = foundCookieValue + } else { + if empty.IsEmpty(foundValue) { + data[foundKey] = foundCookieValue + } } } } diff --git a/net/ghttp/ghttp_server_openapi.go b/net/ghttp/ghttp_server_openapi.go index 0ae5c67baab..1789b4dbe57 100644 --- a/net/ghttp/ghttp_server_openapi.go +++ b/net/ghttp/ghttp_server_openapi.go @@ -28,7 +28,7 @@ func (s *Server) initOpenApi() { case HandlerTypeMiddleware, HandlerTypeHook: continue } - if item.Handler.Info.Func == nil { + if item.Handler.Info.IsStrictRoute { methods = []string{item.Method} if gstr.Equal(item.Method, defaultMethod) { methods = SupportedMethods() diff --git a/net/ghttp/ghttp_server_service_handler.go b/net/ghttp/ghttp_server_service_handler.go index 5c41dcfe0a3..935bf1c7b19 100644 --- a/net/ghttp/ghttp_server_service_handler.go +++ b/net/ghttp/ghttp_server_service_handler.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/text/gstr" ) @@ -145,21 +146,28 @@ func (s *Server) nameToUri(name string) string { } func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, methodName string) (info handlerFuncInfo, err error) { - handlerFunc, ok := f.(HandlerFunc) - if !ok { - reflectType := reflect.TypeOf(f) + info.Type = reflect.TypeOf(f) + info.Value = reflect.ValueOf(f) + if handlerFunc, ok := f.(HandlerFunc); ok { + info.Func = handlerFunc + } else { + var ( + reflectType = info.Type + inputObject reflect.Value + objectPointer interface{} + ) if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 { if pkgPath != "" { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`, - pkgPath, structName, methodName, reflect.TypeOf(f).String(), + pkgPath, structName, methodName, reflectType.String(), ) } else { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`, - reflect.TypeOf(f).String(), + reflectType.String(), ) } return @@ -169,7 +177,26 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`, - reflect.TypeOf(f).String(), + reflectType.String(), + ) + return + } + + if reflectType.In(1).Kind() != reflect.Ptr || + (reflectType.In(1).Kind() == reflect.Ptr && reflectType.In(1).Elem().Kind() != reflect.Struct) { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the second input parameter should be type of pointer to struct like "*BizReq"`, + reflectType.String(), + ) + return + } + + if reflectType.Out(0).Kind() != reflect.Ptr || (reflectType.Out(0).Kind() == reflect.Ptr && reflectType.Out(0).Elem().Kind() != reflect.Struct) { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the first output parameter should be type of pointer to struct like "*BizRes"`, + reflectType.String(), ) return } @@ -178,7 +205,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid handler: defined as "%s", but the last output parameter should be type of "error"`, - reflect.TypeOf(f).String(), + reflectType.String(), ) return } @@ -204,10 +231,62 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth ) return } + + info.IsStrictRoute = true + + inputObject = reflect.New(info.Type.In(1).Elem()) + objectPointer = inputObject.Interface() + + // It retrieves and returns the request struct fields. + fields, err := gstructs.Fields(gstructs.FieldsInput{ + Pointer: objectPointer, + RecursiveOption: gstructs.RecursiveOptionEmbedded, + }) + if err != nil { + return info, err + } + info.ReqStructFields = fields + + // Build handler for processing + info.Func = func(r *Request) { + var ( + inputValues = []reflect.Value{ + reflect.ValueOf(r.Context()), + } + inputObject reflect.Value + objectPointer interface{} + ) + // Must new a new object for each request. + inputObject = reflect.New(reflectType.In(1).Elem()) + objectPointer = inputObject.Interface() + + r.error = r.Parse(objectPointer) + if r.error != nil { + return + } + inputValues = append(inputValues, inputObject) + // Call handler with dynamic created parameter values. + results := info.Value.Call(inputValues) + switch len(results) { + case 1: + // TODO: Is this useful? + if !results[0].IsNil() { + if err, ok := results[0].Interface().(error); ok { + r.error = err + } + } + + case 2: + r.handlerResponse = results[0].Interface() + if !results[1].IsNil() { + if err, ok := results[1].Interface().(error); ok { + r.error = err + } + } + } + } } - info.Func = handlerFunc - info.Type = reflect.TypeOf(f) - info.Value = reflect.ValueOf(f) + return } diff --git a/net/ghttp/ghttp_z_unit_feature_request_param_test.go b/net/ghttp/ghttp_z_unit_feature_request_param_test.go index 27b58515072..b8d77767325 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_param_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_param_test.go @@ -77,9 +77,10 @@ func Benchmark_ParamTag(b *testing.B) { client := g.Client() client.SetPrefix(prefix) client.SetCookie("name", "john") + client.SetHeader("age", "18") b.StartTimer() - for i := 0; i < b.N; i++ { - client.PostContent(ctx, "/user", "key="+strconv.Itoa(i)) + for i := 1; i < b.N; i++ { + client.PostContent(ctx, "/user", "id="+strconv.Itoa(i)) } } diff --git a/net/ghttp/ghttp_z_unit_test.go b/net/ghttp/ghttp_z_unit_test.go index 5f62d4afb0f..b9feeccc3d3 100644 --- a/net/ghttp/ghttp_z_unit_test.go +++ b/net/ghttp/ghttp_z_unit_test.go @@ -11,6 +11,7 @@ import ( "fmt" "net/http" "net/url" + "runtime" "testing" "time" @@ -170,6 +171,10 @@ func Test_BuildParams(t *testing.T) { } func Test_ServerSignal(t *testing.T) { + if runtime.GOOS == "windows" { + t.Log("skip windows") + return + } s := g.Server(guid.S()) s.BindHandler("/", func(r *ghttp.Request) { r.Response.Write("hello world") diff --git a/os/gstructs/gstructs_field_tag.go b/os/gstructs/gstructs_field_tag.go index 2fd039781b8..d213a4ef093 100644 --- a/os/gstructs/gstructs_field_tag.go +++ b/os/gstructs/gstructs_field_tag.go @@ -88,3 +88,9 @@ func (f *Field) TagExample() string { } return v } + +// TagIn returns the most commonly used tag `in` value of the field. +func (f *Field) TagIn() string { + v := f.Tag(gtag.In) + return v +}