From 748040fb0b886cb77fcabfa24293a6d9252f3f35 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 7 Nov 2021 21:31:33 +0800 Subject: [PATCH] improve group router for package ghttp --- frame/gins/gins_server.go | 2 +- net/ghttp/ghttp_server_domain.go | 57 +++-- net/ghttp/ghttp_server_router.go | 46 +++- net/ghttp/ghttp_server_router_group.go | 158 +++++++----- net/ghttp/ghttp_server_router_hook.go | 43 +++- net/ghttp/ghttp_server_router_middleware.go | 32 ++- net/ghttp/ghttp_server_service_handler.go | 54 +++-- net/ghttp/ghttp_server_service_object.go | 225 ++++++++++-------- .../ghttp_unit_router_domain_basic_test.go | 8 +- .../ghttp_unit_router_group_hook_test.go | 30 --- net/ghttp/ghttp_unit_router_group_test.go | 33 --- ...ghttp_unit_router_handler_extended_test.go | 136 +++++++++++ protocol/goai/goai.go | 1 + 13 files changed, 545 insertions(+), 280 deletions(-) diff --git a/frame/gins/gins_server.go b/frame/gins/gins_server.go index 91b0fe38f..e2f35cf46 100644 --- a/frame/gins/gins_server.go +++ b/frame/gins/gins_server.go @@ -60,7 +60,7 @@ func Server(name ...interface{}) *ghttp.Server { } } else { // The configuration is not necessary, so it just prints internal logs. - intlog.Printf(ctx, `missing configuration for HTTP server "%s"`, instanceName) + intlog.Printf(ctx, `missing configuration from configuration component for HTTP server "%s"`, instanceName) } // Server logger configuration checks. diff --git a/net/ghttp/ghttp_server_domain.go b/net/ghttp/ghttp_server_domain.go index e160c0687..74a77c4a1 100644 --- a/net/ghttp/ghttp_server_domain.go +++ b/net/ghttp/ghttp_server_domain.go @@ -35,9 +35,15 @@ func (d *Domain) BindHandler(pattern string, handler interface{}) { } } -func (d *Domain) doBindHandler(ctx context.Context, pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) { +func (d *Domain) doBindHandler(ctx context.Context, in doBindHandlerInput) { for domain, _ := range d.domains { - d.server.doBindHandler(ctx, pattern+"@"+domain, funcInfo, middleware, source) + d.server.doBindHandler(ctx, doBindHandlerInput{ + Prefix: in.Prefix, + Pattern: in.Pattern + "@" + domain, + FuncInfo: in.FuncInfo, + Middleware: in.Middleware, + Source: in.Source, + }) } } @@ -47,9 +53,16 @@ func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string) } } -func (d *Domain) doBindObject(ctx context.Context, pattern string, obj interface{}, methods string, middleware []HandlerFunc, source string) { +func (d *Domain) doBindObject(ctx context.Context, in doBindObjectInput) { for domain, _ := range d.domains { - d.server.doBindObject(ctx, pattern+"@"+domain, obj, methods, middleware, source) + d.server.doBindObject(ctx, doBindObjectInput{ + Prefix: in.Prefix, + Pattern: in.Pattern + "@" + domain, + Object: in.Object, + Method: in.Method, + Middleware: in.Middleware, + Source: in.Source, + }) } } @@ -59,13 +72,16 @@ func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string } } -func (d *Domain) doBindObjectMethod( - ctx context.Context, - pattern string, obj interface{}, method string, - middleware []HandlerFunc, source string, -) { +func (d *Domain) doBindObjectMethod(ctx context.Context, in doBindObjectMethodInput) { for domain, _ := range d.domains { - d.server.doBindObjectMethod(ctx, pattern+"@"+domain, obj, method, middleware, source) + d.server.doBindObjectMethod(ctx, doBindObjectMethodInput{ + Prefix: in.Prefix, + Pattern: in.Pattern + "@" + domain, + Object: in.Object, + Method: in.Method, + Middleware: in.Middleware, + Source: in.Source, + }) } } @@ -75,9 +91,16 @@ func (d *Domain) BindObjectRest(pattern string, obj interface{}) { } } -func (d *Domain) doBindObjectRest(ctx context.Context, pattern string, obj interface{}, middleware []HandlerFunc, source string) { +func (d *Domain) doBindObjectRest(ctx context.Context, in doBindObjectInput) { for domain, _ := range d.domains { - d.server.doBindObjectRest(ctx, pattern+"@"+domain, obj, middleware, source) + d.server.doBindObjectRest(ctx, doBindObjectInput{ + Prefix: in.Prefix, + Pattern: in.Pattern + "@" + domain, + Object: in.Object, + Method: in.Method, + Middleware: in.Middleware, + Source: in.Source, + }) } } @@ -87,9 +110,15 @@ func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFun } } -func (d *Domain) doBindHookHandler(ctx context.Context, pattern string, hook string, handler HandlerFunc, source string) { +func (d *Domain) doBindHookHandler(ctx context.Context, in doBindHookHandlerInput) { for domain, _ := range d.domains { - d.server.doBindHookHandler(ctx, pattern+"@"+domain, hook, handler, source) + d.server.doBindHookHandler(ctx, doBindHookHandlerInput{ + Prefix: in.Prefix, + Pattern: in.Pattern + "@" + domain, + HookName: in.HookName, + Handler: in.Handler, + Source: in.Source, + }) } } diff --git a/net/ghttp/ghttp_server_router.go b/net/ghttp/ghttp_server_router.go index ac6b361ca..50be1545e 100644 --- a/net/ghttp/ghttp_server_router.go +++ b/net/ghttp/ghttp_server_router.go @@ -12,6 +12,9 @@ import ( "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/protocol/goai" + "github.com/gogf/gf/v2/util/gmeta" + "reflect" "strings" "github.com/gogf/gf/v2/debug/gdebug" @@ -63,11 +66,22 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err return } +type setHandlerInput struct { + Prefix string + Pattern string + HandlerItem *handlerItem +} + // setHandler creates router item with given handler and pattern and registers the handler to the router tree. // The router tree can be treated as a multilayer hash table, please refer to the comment in following codes. // This function is called during server starts up, which cares little about the performance. What really cares // is the well-designed router storage structure for router searching when the request is under serving. -func (s *Server) setHandler(ctx context.Context, pattern string, handler *handlerItem) { +func (s *Server) setHandler(ctx context.Context, in setHandlerInput) { + var ( + prefix = in.Prefix + pattern = in.Pattern + handler = in.HandlerItem + ) handler.Id = handlerIdGenerator.Add(1) if handler.Source == "" { _, file, line := gdebug.CallerWithFilter(stackFilterKey) @@ -78,6 +92,28 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle s.Logger().Fatalf(ctx, `invalid pattern "%s", %+v`, pattern, err) return } + + // Change the registered route according meta info from its request structure. + if handler.Info.Type != nil && handler.Info.Type.NumIn() == 2 { + var ( + objectReq = reflect.New(handler.Info.Type.In(1)) + ) + if v := gmeta.Get(objectReq, goai.TagNamePath); !v.IsEmpty() { + uri = v.String() + } + if v := gmeta.Get(objectReq, goai.TagNameMethod); !v.IsEmpty() { + method = v.String() + } + if v := gmeta.Get(objectReq, goai.TagNameDomain); !v.IsEmpty() { + domain = v.String() + } + } + + // Prefix for URI feature. + if prefix != "" { + uri = prefix + "/" + strings.TrimLeft(uri, "/") + } + if len(uri) == 0 || uri[0] != '/' { s.Logger().Fatalf(ctx, `invalid pattern "%s", URI should lead with '/'`, pattern) return @@ -177,7 +213,7 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle for e := l.Front(); e != nil; e = e.Next() { item = e.Value.(*handlerItem) // Checks the priority whether inserting the route item before current item, - // which means it has more higher priority. + // which means it has higher priority. if s.compareRouterPriority(handler, item) { l.InsertBefore(e, handler) pushed = true @@ -211,11 +247,11 @@ func (s *Server) setHandler(ctx context.Context, pattern string, handler *handle // compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true // if `newItem`'s priority is higher than `oldItem`, else it returns false. The higher priority -// item will be insert into the router list before the other one. +// item will be inserted into the router list before the other one. // // Comparison rules: // 1. The middleware has the most high priority. -// 2. URI: The deeper the higher (simply check the count of char '/' in the URI). +// 2. URI: The deeper, the higher (simply check the count of char '/' in the URI). // 3. Route type: {xxx} > :xxx > *xxx. func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerItem) bool { // If they're all type of middleware, the priority is according their registered sequence. @@ -226,7 +262,7 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte if newItem.Type == HandlerTypeMiddleware && oldItem.Type != HandlerTypeMiddleware { return true } - // URI: The deeper the higher (simply check the count of char '/' in the URI). + // URI: The deeper, the higher (simply check the count of char '/' in the URI). if newItem.Router.Priority > oldItem.Router.Priority { return true } diff --git a/net/ghttp/ghttp_server_router_group.go b/net/ghttp/ghttp_server_router_group.go index 4f88fde2f..1e5d7d7e2 100644 --- a/net/ghttp/ghttp_server_router_group.go +++ b/net/ghttp/ghttp_server_router_group.go @@ -10,10 +10,9 @@ import ( "context" "fmt" "github.com/gogf/gf/v2/debug/gdebug" - "reflect" - "strings" - + "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/text/gstr" + "reflect" "github.com/gogf/gf/v2/util/gconv" ) @@ -28,9 +27,6 @@ type ( middleware []HandlerFunc // Middleware array. } - // GroupItem is item for router group. - GroupItem = []interface{} - // preBindItem is item for lazy registering feature of router group. preBindItem is not really registered // to server when route function of the group called but is lazily registered when server starts. preBindItem struct { @@ -104,16 +100,17 @@ func (d *Domain) Group(prefix string, groups ...func(group *RouterGroup)) *Route if prefix == "/" { prefix = "" } - group := &RouterGroup{ + routerGroup := &RouterGroup{ domain: d, + server: d.server, prefix: prefix, } if len(groups) > 0 { - for _, v := range groups { - v(group) + for _, nestedGroup := range groups { + nestedGroup(routerGroup) } } - return group + return routerGroup } // Group creates and returns a sub-group of current router group. @@ -153,34 +150,26 @@ func (g *RouterGroup) Clone() *RouterGroup { } // Bind does batch route registering feature for router group. -func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup { +func (g *RouterGroup) Bind(handlerOrObject ...interface{}) *RouterGroup { var ( ctx = context.TODO() group = g.Clone() ) - for _, item := range items { - if len(item) < 3 { - g.server.Logger().Fatalf(ctx, "invalid router item: %s", item) - } - bindType := gstr.ToUpper(gconv.String(item[0])) - switch bindType { - case groupBindTypeRest: - group.preBindToLocalArray(groupBindTypeRest, gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) - - case groupBindTypeMiddleware: - group.preBindToLocalArray(groupBindTypeMiddleware, gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) + for _, v := range handlerOrObject { + var ( + item = v + originValueAndKind = utils.OriginValueAndKind(item) + ) + switch originValueAndKind.OriginKind { + case reflect.Func, reflect.Struct: + group = group.preBindToLocalArray( + groupBindTypeHandler, + "/", + item, + ) default: - if strings.EqualFold(bindType, "ALL") { - bindType = "" - } else { - bindType += ":" - } - if len(item) > 3 { - group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2], item[3]) - } else { - group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2]) - } + g.server.Logger().Fatalf(ctx, "invalid bind parameter type: %v", originValueAndKind.InputValue.Type()) } } return group @@ -188,7 +177,12 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup { // ALL registers a http handler to given route pattern and all http methods. func (g *RouterGroup) ALL(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray(groupBindTypeHandler, defaultMethod+":"+pattern, object, params...) + return g.Clone().preBindToLocalArray( + groupBindTypeHandler, + defaultMethod+":"+pattern, + object, + params..., + ) } // ALLMap registers http handlers for http methods using map. @@ -312,10 +306,10 @@ func (g *RouterGroup) doBindRoutersToServer(ctx context.Context, item *preBindIt domain = "" } if bindType == groupBindTypeRest { - pattern = prefix + "/" + strings.TrimLeft(path, "/") + pattern = path } else { pattern = g.server.serveHandlerKey( - method, prefix+"/"+strings.TrimLeft(path, "/"), domain, + method, path, domain, ) } } @@ -337,57 +331,95 @@ func (g *RouterGroup) doBindRoutersToServer(ctx context.Context, item *preBindIt g.server.Logger().Fatal(ctx, err.Error()) return g } - if g.server != nil { - g.server.doBindHandler(ctx, pattern, funcInfo, g.middleware, source) + in := doBindHandlerInput{ + Prefix: prefix, + Pattern: pattern, + FuncInfo: funcInfo, + Middleware: g.middleware, + Source: source, + } + if g.domain != nil { + g.domain.doBindHandler(ctx, in) } else { - g.domain.doBindHandler(ctx, pattern, funcInfo, g.middleware, source) + g.server.doBindHandler(ctx, in) } } else { if len(extras) > 0 { - if g.server != nil { - if gstr.Contains(extras[0], ",") { - g.server.doBindObject( - ctx, pattern, object, extras[0], g.middleware, source, - ) + if gstr.Contains(extras[0], ",") { + in := doBindObjectInput{ + Prefix: prefix, + Pattern: pattern, + Object: object, + Method: extras[0], + Middleware: g.middleware, + Source: source, + } + if g.domain != nil { + g.domain.doBindObject(ctx, in) } else { - g.server.doBindObjectMethod( - ctx, pattern, object, extras[0], g.middleware, source, - ) + g.server.doBindObject(ctx, in) } } else { - if gstr.Contains(extras[0], ",") { - g.domain.doBindObject( - ctx, pattern, object, extras[0], g.middleware, source, - ) + in := doBindObjectMethodInput{ + Prefix: prefix, + Pattern: pattern, + Object: object, + Method: extras[0], + Middleware: g.middleware, + Source: source, + } + if g.domain != nil { + g.domain.doBindObjectMethod(ctx, in) } else { - g.domain.doBindObjectMethod( - ctx, pattern, object, extras[0], g.middleware, source, - ) + g.server.doBindObjectMethod(ctx, in) } } } else { + in := doBindObjectInput{ + Prefix: prefix, + Pattern: pattern, + Object: object, + Method: "", + Middleware: g.middleware, + Source: source, + } // At last, it treats the `object` as Object registering type. - if g.server != nil { - g.server.doBindObject(ctx, pattern, object, "", g.middleware, source) + if g.domain != nil { + g.domain.doBindObject(ctx, in) } else { - g.domain.doBindObject(ctx, pattern, object, "", g.middleware, source) + g.server.doBindObject(ctx, in) } } } case groupBindTypeRest: - if g.server != nil { - g.server.doBindObjectRest(ctx, pattern, object, g.middleware, source) + in := doBindObjectInput{ + Prefix: prefix, + Pattern: pattern, + Object: object, + Method: "", + Middleware: g.middleware, + Source: source, + } + if g.domain != nil { + g.domain.doBindObjectRest(ctx, in) } else { - g.domain.doBindObjectRest(ctx, pattern, object, g.middleware, source) + g.server.doBindObjectRest(ctx, in) } case groupBindTypeHook: - if h, ok := object.(HandlerFunc); ok { - if g.server != nil { - g.server.doBindHookHandler(ctx, pattern, extras[0], h, source) + if handler, ok := object.(HandlerFunc); ok { + in := doBindHookHandlerInput{ + Prefix: prefix, + Pattern: pattern, + HookName: extras[0], + Handler: handler, + Source: source, + } + if g.domain != nil { + g.domain.doBindHookHandler(ctx, in) } else { - g.domain.doBindHookHandler(ctx, pattern, extras[0], h, source) + g.server.doBindHookHandler(ctx, in) } } else { g.server.Logger().Fatalf(ctx, "invalid hook handler for pattern: %s", pattern) diff --git a/net/ghttp/ghttp_server_router_hook.go b/net/ghttp/ghttp_server_router_hook.go index 2138af086..2be2dd087 100644 --- a/net/ghttp/ghttp_server_router_hook.go +++ b/net/ghttp/ghttp_server_router_hook.go @@ -15,20 +15,41 @@ import ( // BindHookHandler registers handler for specified hook. func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) { - s.doBindHookHandler(context.TODO(), pattern, hook, handler, "") + s.doBindHookHandler(context.TODO(), doBindHookHandlerInput{ + Prefix: "", + Pattern: pattern, + HookName: hook, + Handler: handler, + Source: "", + }) } -func (s *Server) doBindHookHandler(ctx context.Context, pattern string, hook string, handler HandlerFunc, source string) { - s.setHandler(ctx, pattern, &handlerItem{ - Type: HandlerTypeHook, - Name: gdebug.FuncPath(handler), - Info: handlerFuncInfo{ - Func: handler, - Type: reflect.TypeOf(handler), +type doBindHookHandlerInput struct { + Prefix string + Pattern string + HookName string + Handler HandlerFunc + Source string +} + +func (s *Server) doBindHookHandler(ctx context.Context, in doBindHookHandlerInput) { + s.setHandler( + ctx, + setHandlerInput{ + Prefix: in.Prefix, + Pattern: in.Pattern, + HandlerItem: &handlerItem{ + Type: HandlerTypeHook, + Name: gdebug.FuncPath(in.Handler), + Info: handlerFuncInfo{ + Func: in.Handler, + Type: reflect.TypeOf(in.Handler), + }, + HookName: in.HookName, + Source: in.Source, + }, }, - HookName: hook, - Source: source, - }) + ) } func (s *Server) BindHookHandlerByMap(pattern string, hookMap map[string]HandlerFunc) { diff --git a/net/ghttp/ghttp_server_router_middleware.go b/net/ghttp/ghttp_server_router_middleware.go index 2e4c99f0b..99fb2acaf 100644 --- a/net/ghttp/ghttp_server_router_middleware.go +++ b/net/ghttp/ghttp_server_router_middleware.go @@ -26,12 +26,16 @@ func (s *Server) BindMiddleware(pattern string, handlers ...HandlerFunc) { ctx = context.TODO() ) for _, handler := range handlers { - s.setHandler(ctx, pattern, &handlerItem{ - Type: HandlerTypeMiddleware, - Name: gdebug.FuncPath(handler), - Info: handlerFuncInfo{ - Func: handler, - Type: reflect.TypeOf(handler), + s.setHandler(ctx, setHandlerInput{ + Prefix: "", + Pattern: pattern, + HandlerItem: &handlerItem{ + Type: HandlerTypeMiddleware, + Name: gdebug.FuncPath(handler), + Info: handlerFuncInfo{ + Func: handler, + Type: reflect.TypeOf(handler), + }, }, }) } @@ -45,12 +49,16 @@ func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc) { ctx = context.TODO() ) for _, handler := range handlers { - s.setHandler(ctx, defaultMiddlewarePattern, &handlerItem{ - Type: HandlerTypeMiddleware, - Name: gdebug.FuncPath(handler), - Info: handlerFuncInfo{ - Func: handler, - Type: reflect.TypeOf(handler), + s.setHandler(ctx, setHandlerInput{ + Prefix: "", + Pattern: defaultMiddlewarePattern, + HandlerItem: &handlerItem{ + Type: HandlerTypeMiddleware, + Name: gdebug.FuncPath(handler), + Info: handlerFuncInfo{ + Func: handler, + Type: reflect.TypeOf(handler), + }, }, }) } diff --git a/net/ghttp/ghttp_server_service_handler.go b/net/ghttp/ghttp_server_service_handler.go index 0c87469e9..281f3a45c 100644 --- a/net/ghttp/ghttp_server_service_handler.go +++ b/net/ghttp/ghttp_server_service_handler.go @@ -19,9 +19,10 @@ import ( ) // BindHandler registers a handler function to server with given pattern. -// The parameter `handler` can be type of: -// func(*ghttp.Request) -// func(context.Context, Request)(Response, error) +// +// Note that the parameter `handler` can be type of: +// 1. func(*ghttp.Request) +// 2. func(context.Context, BizRequest)(BizResponse, error) func (s *Server) BindHandler(pattern string, handler interface{}) { var ( ctx = context.TODO() @@ -30,26 +31,49 @@ func (s *Server) BindHandler(pattern string, handler interface{}) { if err != nil { s.Logger().Fatalf(ctx, `%+v`, err) } - s.doBindHandler(ctx, pattern, funcInfo, nil, "") + s.doBindHandler(ctx, doBindHandlerInput{ + Prefix: "", + Pattern: pattern, + FuncInfo: funcInfo, + Middleware: nil, + Source: "", + }) +} + +type doBindHandlerInput struct { + Prefix string + Pattern string + FuncInfo handlerFuncInfo + Middleware []HandlerFunc + Source string } // doBindHandler registers a handler function to server with given pattern. +// // The parameter `pattern` is like: // /user/list, put:/user, delete:/user, post:/user@goframe.org -func (s *Server) doBindHandler(ctx context.Context, pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) { - s.setHandler(ctx, pattern, &handlerItem{ - Name: gdebug.FuncPath(funcInfo.Func), - Type: HandlerTypeHandler, - Info: funcInfo, - Middleware: middleware, - Source: source, +func (s *Server) doBindHandler(ctx context.Context, in doBindHandlerInput) { + s.setHandler(ctx, setHandlerInput{ + Prefix: in.Prefix, + Pattern: in.Pattern, + HandlerItem: &handlerItem{ + Name: gdebug.FuncPath(in.FuncInfo.Func), + Type: HandlerTypeHandler, + Info: in.FuncInfo, + Middleware: in.Middleware, + Source: in.Source, + }, }) } // bindHandlerByMap registers handlers to server using map. -func (s *Server) bindHandlerByMap(ctx context.Context, m map[string]*handlerItem) { - for p, h := range m { - s.setHandler(ctx, p, h) +func (s *Server) bindHandlerByMap(ctx context.Context, prefix string, m map[string]*handlerItem) { + for pattern, handler := range m { + s.setHandler(ctx, setHandlerInput{ + Prefix: prefix, + Pattern: pattern, + HandlerItem: handler, + }) } } @@ -162,6 +186,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth return } + // The request struct should be named as `xxxReq`. if !gstr.HasSuffix(reflectType.In(1).String(), `Req`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, @@ -171,6 +196,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth return } + // The response struct should be named as `xxxRes`. if !gstr.HasSuffix(reflectType.Out(0).String(), `Res`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, diff --git a/net/ghttp/ghttp_server_service_object.go b/net/ghttp/ghttp_server_service_object.go index eea76edcd..13e141fab 100644 --- a/net/ghttp/ghttp_server_service_object.go +++ b/net/ghttp/ghttp_server_service_object.go @@ -21,8 +21,6 @@ import ( // // The optional parameter `method` is used to specify the method to be registered, which // supports multiple method names, multiple methods are separated by char ',', case-sensitive. -// -// Note that the route method should be defined as ghttp.HandlerFunc. func (s *Server) BindObject(pattern string, object interface{}, method ...string) { var ( bindMethod = "" @@ -30,97 +28,124 @@ func (s *Server) BindObject(pattern string, object interface{}, method ...string if len(method) > 0 { bindMethod = method[0] } - s.doBindObject(context.TODO(), pattern, object, bindMethod, nil, "") + s.doBindObject(context.TODO(), doBindObjectInput{ + Prefix: "", + Pattern: pattern, + Object: object, + Method: bindMethod, + Middleware: nil, + Source: "", + }) } // BindObjectMethod registers specified method of object to server routes with given pattern. // // The optional parameter `method` is used to specify the method to be registered, which // does not supports multiple method names but only one, case-sensitive. -// -// Note that the route method should be defined as ghttp.HandlerFunc. func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) { - s.doBindObjectMethod(context.TODO(), pattern, object, method, nil, "") + s.doBindObjectMethod(context.TODO(), doBindObjectMethodInput{ + Prefix: "", + Pattern: pattern, + Object: object, + Method: method, + Middleware: nil, + Source: "", + }) } // BindObjectRest registers object in REST API styles to server with specified pattern. -// Note that the route method should be defined as ghttp.HandlerFunc. func (s *Server) BindObjectRest(pattern string, object interface{}) { - s.doBindObjectRest(context.TODO(), pattern, object, nil, "") + s.doBindObjectRest(context.TODO(), doBindObjectInput{ + Prefix: "", + Pattern: pattern, + Object: object, + Method: "", + Middleware: nil, + Source: "", + }) } -func (s *Server) doBindObject(ctx context.Context, pattern string, object interface{}, method string, middleware []HandlerFunc, source string) { +type doBindObjectInput struct { + Prefix string + Pattern string + Object interface{} + Method string + Middleware []HandlerFunc + Source string +} + +func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) { // Convert input method to map for convenience and high performance searching purpose. var ( methodMap map[string]bool ) - if len(method) > 0 { + if len(in.Method) > 0 { methodMap = make(map[string]bool) - for _, v := range strings.Split(method, ",") { + for _, v := range strings.Split(in.Method, ",") { methodMap[strings.TrimSpace(v)] = true } } // If the `method` in `pattern` is `defaultMethod`, // it removes for convenience for next statement control. - domain, method, path, err := s.parsePattern(pattern) + domain, method, path, err := s.parsePattern(in.Pattern) if err != nil { s.Logger().Fatalf(ctx, `%+v`, err) return } if strings.EqualFold(method, defaultMethod) { - pattern = s.serveHandlerKey("", path, domain) + in.Pattern = s.serveHandlerKey("", path, domain) } var ( - m = make(map[string]*handlerItem) - v = reflect.ValueOf(object) - t = v.Type() - initFunc func(*Request) - shutFunc func(*Request) + handlerMap = make(map[string]*handlerItem) + reflectValue = reflect.ValueOf(in.Object) + reflectType = reflectValue.Type() + initFunc func(*Request) + shutFunc func(*Request) ) // If given `object` is not pointer, it then creates a temporary one, // of which the value is `v`. - if v.Kind() == reflect.Struct { - newValue := reflect.New(t) - newValue.Elem().Set(v) - v = newValue - t = v.Type() + if reflectValue.Kind() == reflect.Struct { + newValue := reflect.New(reflectType) + newValue.Elem().Set(reflectValue) + reflectValue = newValue + reflectType = reflectValue.Type() } - structName := t.Elem().Name() - if v.MethodByName("Init").IsValid() { - initFunc = v.MethodByName("Init").Interface().(func(*Request)) + structName := reflectType.Elem().Name() + if reflectValue.MethodByName("Init").IsValid() { + initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request)) } - if v.MethodByName("Shut").IsValid() { - shutFunc = v.MethodByName("Shut").Interface().(func(*Request)) + if reflectValue.MethodByName("Shut").IsValid() { + shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request)) } - pkgPath := t.Elem().PkgPath() + pkgPath := reflectType.Elem().PkgPath() pkgName := gfile.Basename(pkgPath) - for i := 0; i < v.NumMethod(); i++ { - methodName := t.Method(i).Name + for i := 0; i < reflectValue.NumMethod(); i++ { + methodName := reflectType.Method(i).Name if methodMap != nil && !methodMap[methodName] { continue } if methodName == "Init" || methodName == "Shut" { continue } - objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") + objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "") if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) } - funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName) + funcInfo, err := s.checkAndCreateFuncInfo(reflectValue.Method(i).Interface(), pkgPath, objName, methodName) if err != nil { s.Logger().Fatalf(ctx, `%+v`, err) } - key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true) - m[key] = &handlerItem{ + key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, true) + handlerMap[key] = &handlerItem{ Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), Type: HandlerTypeObject, Info: funcInfo, InitFunc: initFunc, ShutFunc: shutFunc, - Middleware: middleware, - Source: source, + Middleware: in.Middleware, + Source: in.Source, } // If there's "Index" method, then an additional route is automatically added // to match the main URI, for example: @@ -129,61 +154,70 @@ func (s *Server) doBindObject(ctx context.Context, pattern string, object interf // // Note that if there's built-in variables in pattern, this route will not be added // automatically. - if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) { + if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, in.Pattern) { p := gstr.PosRI(key, "/index") k := key[0:p] + key[p+6:] if len(k) == 0 || k[0] == '@' { k = "/" + k } - m[k] = &handlerItem{ + handlerMap[k] = &handlerItem{ Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), Type: HandlerTypeObject, Info: funcInfo, InitFunc: initFunc, ShutFunc: shutFunc, - Middleware: middleware, - Source: source, + Middleware: in.Middleware, + Source: in.Source, } } } - s.bindHandlerByMap(ctx, m) + s.bindHandlerByMap(ctx, in.Prefix, handlerMap) } -func (s *Server) doBindObjectMethod(ctx context.Context, pattern string, object interface{}, method string, middleware []HandlerFunc, source string) { +type doBindObjectMethodInput struct { + Prefix string + Pattern string + Object interface{} + Method string + Middleware []HandlerFunc + Source string +} + +func (s *Server) doBindObjectMethod(ctx context.Context, in doBindObjectMethodInput) { var ( - m = make(map[string]*handlerItem) - v = reflect.ValueOf(object) - t = v.Type() - initFunc func(*Request) - shutFunc func(*Request) + handlerMap = make(map[string]*handlerItem) + reflectValue = reflect.ValueOf(in.Object) + reflectType = reflectValue.Type() + initFunc func(*Request) + shutFunc func(*Request) ) // If given `object` is not pointer, it then creates a temporary one, // of which the value is `v`. - if v.Kind() == reflect.Struct { - newValue := reflect.New(t) - newValue.Elem().Set(v) - v = newValue - t = v.Type() + if reflectValue.Kind() == reflect.Struct { + newValue := reflect.New(reflectType) + newValue.Elem().Set(reflectValue) + reflectValue = newValue + reflectType = reflectValue.Type() } var ( - structName = t.Elem().Name() - methodName = strings.TrimSpace(method) - methodValue = v.MethodByName(methodName) + structName = reflectType.Elem().Name() + methodName = strings.TrimSpace(in.Method) + methodValue = reflectValue.MethodByName(methodName) ) if !methodValue.IsValid() { s.Logger().Fatalf(ctx, "invalid method name: %s", methodName) return } - if v.MethodByName("Init").IsValid() { - initFunc = v.MethodByName("Init").Interface().(func(*Request)) + if reflectValue.MethodByName("Init").IsValid() { + initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request)) } - if v.MethodByName("Shut").IsValid() { - shutFunc = v.MethodByName("Shut").Interface().(func(*Request)) + if reflectValue.MethodByName("Shut").IsValid() { + shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request)) } var ( - pkgPath = t.Elem().PkgPath() + pkgPath = reflectType.Elem().PkgPath() pkgName = gfile.Basename(pkgPath) - objName = gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") + objName = gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "") ) if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) @@ -194,70 +228,75 @@ func (s *Server) doBindObjectMethod(ctx context.Context, pattern string, object s.Logger().Fatalf(ctx, `%+v`, err) } - key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false) - m[key] = &handlerItem{ + key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, false) + handlerMap[key] = &handlerItem{ Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), Type: HandlerTypeObject, Info: funcInfo, InitFunc: initFunc, ShutFunc: shutFunc, - Middleware: middleware, - Source: source, + Middleware: in.Middleware, + Source: in.Source, } - s.bindHandlerByMap(ctx, m) + s.bindHandlerByMap(ctx, in.Prefix, handlerMap) } -func (s *Server) doBindObjectRest(ctx context.Context, pattern string, object interface{}, middleware []HandlerFunc, source string) { +func (s *Server) doBindObjectRest(ctx context.Context, in doBindObjectInput) { var ( - m = make(map[string]*handlerItem) - v = reflect.ValueOf(object) - t = v.Type() - initFunc func(*Request) - shutFunc func(*Request) + handlerMap = make(map[string]*handlerItem) + reflectValue = reflect.ValueOf(in.Object) + reflectType = reflectValue.Type() + initFunc func(*Request) + shutFunc func(*Request) ) // If given `object` is not pointer, it then creates a temporary one, // of which the value is `v`. - if v.Kind() == reflect.Struct { - newValue := reflect.New(t) - newValue.Elem().Set(v) - v = newValue - t = v.Type() + if reflectValue.Kind() == reflect.Struct { + newValue := reflect.New(reflectType) + newValue.Elem().Set(reflectValue) + reflectValue = newValue + reflectType = reflectValue.Type() } - structName := t.Elem().Name() - if v.MethodByName(methodNameInit).IsValid() { - initFunc = v.MethodByName(methodNameInit).Interface().(func(*Request)) + structName := reflectType.Elem().Name() + if reflectValue.MethodByName(methodNameInit).IsValid() { + initFunc = reflectValue.MethodByName(methodNameInit).Interface().(func(*Request)) } - if v.MethodByName(methodNameShut).IsValid() { - shutFunc = v.MethodByName(methodNameShut).Interface().(func(*Request)) + if reflectValue.MethodByName(methodNameShut).IsValid() { + shutFunc = reflectValue.MethodByName(methodNameShut).Interface().(func(*Request)) } - pkgPath := t.Elem().PkgPath() - for i := 0; i < v.NumMethod(); i++ { - methodName := t.Method(i).Name + pkgPath := reflectType.Elem().PkgPath() + for i := 0; i < reflectValue.NumMethod(); i++ { + methodName := reflectType.Method(i).Name if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok { continue } pkgName := gfile.Basename(pkgPath) - objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") + objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "") if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) } - funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName) + funcInfo, err := s.checkAndCreateFuncInfo( + reflectValue.Method(i).Interface(), + pkgPath, + objName, + methodName, + ) if err != nil { s.Logger().Fatalf(ctx, `%+v`, err) } - key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false) - m[key] = &handlerItem{ + key := s.mergeBuildInNameToPattern(methodName+":"+in.Pattern, structName, methodName, false) + handlerMap[key] = &handlerItem{ Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), Type: HandlerTypeObject, Info: funcInfo, InitFunc: initFunc, ShutFunc: shutFunc, - Middleware: middleware, - Source: source, + Middleware: in.Middleware, + Source: in.Source, } } - s.bindHandlerByMap(ctx, m) + s.bindHandlerByMap(ctx, in.Prefix, handlerMap) } diff --git a/net/ghttp/ghttp_unit_router_domain_basic_test.go b/net/ghttp/ghttp_unit_router_domain_basic_test.go index fd96cb3e5..1c589cc22 100644 --- a/net/ghttp/ghttp_unit_router_domain_basic_test.go +++ b/net/ghttp/ghttp_unit_router_domain_basic_test.go @@ -333,16 +333,16 @@ func Test_Router_DomainGroup(t *testing.T) { s := g.Server(p) d := s.Domain("localhost, local") d.Group("/", func(group *ghttp.RouterGroup) { - group.Group("/app", func(gApp *ghttp.RouterGroup) { - gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) { + group.Group("/app", func(group *ghttp.RouterGroup) { + group.GET("/{table}/list/{page}.html", func(r *ghttp.Request) { intlog.Print(r.Context(), "/{table}/list/{page}.html") r.Response.Write(r.Get("table"), "&", r.Get("page")) }) - gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) { + group.GET("/order/info/{order_id}", func(r *ghttp.Request) { intlog.Print(r.Context(), "/order/info/{order_id}") r.Response.Write(r.Get("order_id")) }) - gApp.DELETE("/comment/{id}", func(r *ghttp.Request) { + group.DELETE("/comment/{id}", func(r *ghttp.Request) { intlog.Print(r.Context(), "/comment/{id}") r.Response.Write(r.Get("id")) }) diff --git a/net/ghttp/ghttp_unit_router_group_hook_test.go b/net/ghttp/ghttp_unit_router_group_hook_test.go index 9a7d6be9e..edea6a80d 100644 --- a/net/ghttp/ghttp_unit_router_group_hook_test.go +++ b/net/ghttp/ghttp_unit_router_group_hook_test.go @@ -74,33 +74,3 @@ func Test_Router_Group_Hook2(t *testing.T) { t.Assert(client.PostContent(ctx, "/api/ThisDoesNotExist"), "Not Found") }) } - -func Test_Router_Group_Hook3(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.Group("/api").Bind([]g.Slice{ - {"ALL", "handler", func(r *ghttp.Request) { - r.Response.Write("1") - }}, - {"ALL", "/*", func(r *ghttp.Request) { - r.Response.Write("0") - }, ghttp.HookBeforeServe}, - {"ALL", "/*", func(r *ghttp.Request) { - r.Response.Write("2") - }, ghttp.HookAfterServe}, - }) - - s.SetPort(p) - s.SetDumpRouterMap(false) - s.Start() - defer s.Shutdown() - - time.Sleep(100 * time.Millisecond) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - t.Assert(client.GetContent(ctx, "/api/handler"), "012") - t.Assert(client.PostContent(ctx, "/api/handler"), "012") - t.Assert(client.DeleteContent(ctx, "/api/ThisDoesNotExist"), "02") - }) -} diff --git a/net/ghttp/ghttp_unit_router_group_test.go b/net/ghttp/ghttp_unit_router_group_test.go index ef2dabf12..195d8da99 100644 --- a/net/ghttp/ghttp_unit_router_group_test.go +++ b/net/ghttp/ghttp_unit_router_group_test.go @@ -78,39 +78,6 @@ func Test_Router_GroupBasic1(t *testing.T) { }) } -func Test_Router_GroupBasic2(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - obj := new(GroupObject) - // 分组路由批量注册 - s.Group("/api").Bind([]g.Slice{ - {"ALL", "/handler", Handler}, - {"ALL", "/obj", obj}, - {"GET", "/obj/my-show", obj, "Show"}, - {"REST", "/obj/rest", obj}, - }) - s.SetPort(p) - s.SetDumpRouterMap(false) - s.Start() - defer s.Shutdown() - - time.Sleep(100 * time.Millisecond) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - - t.Assert(client.GetContent(ctx, "/api/handler"), "Handler") - - t.Assert(client.GetContent(ctx, "/api/obj/delete"), "1Object Delete2") - t.Assert(client.GetContent(ctx, "/api/obj/my-show"), "1Object Show2") - t.Assert(client.GetContent(ctx, "/api/obj/show"), "1Object Show2") - t.Assert(client.DeleteContent(ctx, "/api/obj/rest"), "1Object Delete2") - - t.Assert(client.DeleteContent(ctx, "/ThisDoesNotExist"), "Not Found") - t.Assert(client.DeleteContent(ctx, "/api/ThisDoesNotExist"), "Not Found") - }) -} - func Test_Router_GroupBuildInVar(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) diff --git a/net/ghttp/ghttp_unit_router_handler_extended_test.go b/net/ghttp/ghttp_unit_router_handler_extended_test.go index 801b3c358..0b0c3c937 100644 --- a/net/ghttp/ghttp_unit_router_handler_extended_test.go +++ b/net/ghttp/ghttp_unit_router_handler_extended_test.go @@ -59,3 +59,139 @@ func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) { t.Assert(client.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":null}`) }) } + +type TestForHandlerWithObjectAndMeta1Req struct { + g.Meta `path:"/custom-test1" method:"get"` + Age int + Name string +} +type TestForHandlerWithObjectAndMeta1Res struct { + Id int + Age int +} + +type TestForHandlerWithObjectAndMeta2Req struct { + g.Meta `path:"/custom-test2" method:"get"` + Age int + Name string +} +type TestForHandlerWithObjectAndMeta2Res struct { + Id int + Name string +} + +type ControllerForHandlerWithObjectAndMeta1 struct{} + +func (ControllerForHandlerWithObjectAndMeta1) Test1(ctx context.Context, req *TestForHandlerWithObjectAndMeta1Req) (res *TestForHandlerWithObjectAndMeta1Res, err error) { + return &TestForHandlerWithObjectAndMeta1Res{ + Id: 1, + Age: req.Age, + }, nil +} + +func (ControllerForHandlerWithObjectAndMeta1) Test2(ctx context.Context, req *TestForHandlerWithObjectAndMeta2Req) (res *TestForHandlerWithObjectAndMeta2Res, err error) { + return &TestForHandlerWithObjectAndMeta2Res{ + Id: 1, + Name: req.Name, + }, nil +} + +type TestForHandlerWithObjectAndMeta3Req struct { + g.Meta `path:"/custom-test3" method:"get"` + Age int + Name string +} +type TestForHandlerWithObjectAndMeta3Res struct { + Id int + Age int +} + +type TestForHandlerWithObjectAndMeta4Req struct { + g.Meta `path:"/custom-test4" method:"get"` + Age int + Name string +} +type TestForHandlerWithObjectAndMeta4Res struct { + Id int + Name string +} + +type ControllerForHandlerWithObjectAndMeta2 struct{} + +func (ControllerForHandlerWithObjectAndMeta2) Test3(ctx context.Context, req *TestForHandlerWithObjectAndMeta3Req) (res *TestForHandlerWithObjectAndMeta3Res, err error) { + return &TestForHandlerWithObjectAndMeta3Res{ + Id: 1, + Age: req.Age, + }, nil +} + +func (ControllerForHandlerWithObjectAndMeta2) Test4(ctx context.Context, req *TestForHandlerWithObjectAndMeta4Req) (res *TestForHandlerWithObjectAndMeta4Res, err error) { + return &TestForHandlerWithObjectAndMeta4Res{ + Id: 1, + Name: req.Name, + }, nil +} +func Test_Router_Handler_Extended_Handler_WithObjectAndMeta(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.Use(ghttp.MiddlewareHandlerResponse) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", new(ControllerForHandlerWithObjectAndMeta1)) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`) + t.Assert(client.GetContent(ctx, "/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`) + t.Assert(client.GetContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`) + t.Assert(client.PostContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`) + }) +} + +func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.Use(ghttp.MiddlewareHandlerResponse) + s.Group("/api/v1", func(group *ghttp.RouterGroup) { + group.Bind( + new(ControllerForHandlerWithObjectAndMeta1), + new(ControllerForHandlerWithObjectAndMeta2), + ) + }) + s.Group("/api/v2", func(group *ghttp.RouterGroup) { + group.Bind( + new(ControllerForHandlerWithObjectAndMeta1), + new(ControllerForHandlerWithObjectAndMeta2), + ) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`) + t.Assert(client.GetContent(ctx, "/api/v1/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`) + t.Assert(client.GetContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`) + t.Assert(client.PostContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`) + + t.Assert(client.GetContent(ctx, "/api/v1/custom-test3?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`) + t.Assert(client.GetContent(ctx, "/api/v1/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`) + + t.Assert(client.GetContent(ctx, "/api/v2/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`) + t.Assert(client.GetContent(ctx, "/api/v2/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`) + t.Assert(client.GetContent(ctx, "/api/v2/custom-test3?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`) + t.Assert(client.GetContent(ctx, "/api/v2/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`) + }) +} diff --git a/protocol/goai/goai.go b/protocol/goai/goai.go index 1f9036c57..231123b86 100644 --- a/protocol/goai/goai.go +++ b/protocol/goai/goai.go @@ -82,6 +82,7 @@ const ( TagNameMethod = `method` TagNameMime = `mime` TagNameType = `type` + TagNameDomain = `domain` TagNameValidate = `v` )