improve group router for package ghttp

This commit is contained in:
John Guo
2021-11-07 21:31:33 +08:00
parent 3ca2cad449
commit 748040fb0b
13 changed files with 545 additions and 280 deletions

View File

@ -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.

View File

@ -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,
})
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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) {

View File

@ -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),
},
},
})
}

View File

@ -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,

View File

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

View File

@ -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"))
})

View File

@ -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")
})
}

View File

@ -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)

View File

@ -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"}}`)
})
}

View File

@ -82,6 +82,7 @@ const (
TagNameMethod = `method`
TagNameMime = `mime`
TagNameType = `type`
TagNameDomain = `domain`
TagNameValidate = `v`
)