github.com/wangyougui/gf/v2@v2.6.5/net/ghttp/ghttp_server_service_object.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package ghttp
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"reflect"
    13  	"strings"
    14  
    15  	"github.com/wangyougui/gf/v2/os/gfile"
    16  	"github.com/wangyougui/gf/v2/text/gregex"
    17  	"github.com/wangyougui/gf/v2/text/gstr"
    18  )
    19  
    20  // BindObject registers object to server routes with a given pattern.
    21  //
    22  // The optional parameter `method` is used to specify the method to be registered, which
    23  // supports multiple method names; multiple methods are separated by char ',', case-sensitive.
    24  func (s *Server) BindObject(pattern string, object interface{}, method ...string) {
    25  	var bindMethod = ""
    26  	if len(method) > 0 {
    27  		bindMethod = method[0]
    28  	}
    29  	s.doBindObject(context.TODO(), doBindObjectInput{
    30  		Prefix:     "",
    31  		Pattern:    pattern,
    32  		Object:     object,
    33  		Method:     bindMethod,
    34  		Middleware: nil,
    35  		Source:     "",
    36  	})
    37  }
    38  
    39  // BindObjectMethod registers specified method of the object to server routes with a given pattern.
    40  //
    41  // The optional parameter `method` is used to specify the method to be registered, which
    42  // does not support multiple method names but only one, case-sensitive.
    43  func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) {
    44  	s.doBindObjectMethod(context.TODO(), doBindObjectMethodInput{
    45  		Prefix:     "",
    46  		Pattern:    pattern,
    47  		Object:     object,
    48  		Method:     method,
    49  		Middleware: nil,
    50  		Source:     "",
    51  	})
    52  }
    53  
    54  // BindObjectRest registers object in REST API styles to server with a specified pattern.
    55  func (s *Server) BindObjectRest(pattern string, object interface{}) {
    56  	s.doBindObjectRest(context.TODO(), doBindObjectInput{
    57  		Prefix:     "",
    58  		Pattern:    pattern,
    59  		Object:     object,
    60  		Method:     "",
    61  		Middleware: nil,
    62  		Source:     "",
    63  	})
    64  }
    65  
    66  type doBindObjectInput struct {
    67  	Prefix     string
    68  	Pattern    string
    69  	Object     interface{}
    70  	Method     string
    71  	Middleware []HandlerFunc
    72  	Source     string
    73  }
    74  
    75  func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
    76  	// Convert input method to map for convenience and high performance searching purpose.
    77  	var methodMap map[string]bool
    78  	if len(in.Method) > 0 {
    79  		methodMap = make(map[string]bool)
    80  		for _, v := range strings.Split(in.Method, ",") {
    81  			methodMap[strings.TrimSpace(v)] = true
    82  		}
    83  	}
    84  	// If the `method` in `pattern` is `defaultMethod`,
    85  	// it removes for convenience for next statement control.
    86  	domain, method, path, err := s.parsePattern(in.Pattern)
    87  	if err != nil {
    88  		s.Logger().Fatalf(ctx, `%+v`, err)
    89  		return
    90  	}
    91  	if gstr.Equal(method, defaultMethod) {
    92  		in.Pattern = s.serveHandlerKey("", path, domain)
    93  	}
    94  	var (
    95  		handlerMap   = make(map[string]*HandlerItem)
    96  		reflectValue = reflect.ValueOf(in.Object)
    97  		reflectType  = reflectValue.Type()
    98  		initFunc     func(*Request)
    99  		shutFunc     func(*Request)
   100  	)
   101  	// If given `object` is not pointer, it then creates a temporary one,
   102  	// of which the value is `reflectValue`.
   103  	// It then can retrieve all the methods both of struct/*struct.
   104  	if reflectValue.Kind() == reflect.Struct {
   105  		newValue := reflect.New(reflectType)
   106  		newValue.Elem().Set(reflectValue)
   107  		reflectValue = newValue
   108  		reflectType = reflectValue.Type()
   109  	}
   110  	structName := reflectType.Elem().Name()
   111  	if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
   112  		initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
   113  	}
   114  	if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
   115  		shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
   116  	}
   117  	pkgPath := reflectType.Elem().PkgPath()
   118  	pkgName := gfile.Basename(pkgPath)
   119  	for i := 0; i < reflectValue.NumMethod(); i++ {
   120  		methodName := reflectType.Method(i).Name
   121  		if methodMap != nil && !methodMap[methodName] {
   122  			continue
   123  		}
   124  		if methodName == specialMethodNameInit || methodName == specialMethodNameShut {
   125  			continue
   126  		}
   127  		objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
   128  		if objName[0] == '*' {
   129  			objName = fmt.Sprintf(`(%s)`, objName)
   130  		}
   131  
   132  		funcInfo, err := s.checkAndCreateFuncInfo(reflectValue.Method(i).Interface(), pkgPath, objName, methodName)
   133  		if err != nil {
   134  			s.Logger().Fatalf(ctx, `%+v`, err)
   135  		}
   136  
   137  		key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, true)
   138  		handlerMap[key] = &HandlerItem{
   139  			Name:       fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
   140  			Type:       HandlerTypeObject,
   141  			Info:       funcInfo,
   142  			InitFunc:   initFunc,
   143  			ShutFunc:   shutFunc,
   144  			Middleware: in.Middleware,
   145  			Source:     in.Source,
   146  		}
   147  		// If there's "Index" method, then an additional route is automatically added
   148  		// to match the main URI, for example:
   149  		// If pattern is "/user", then "/user" and "/user/index" are both automatically
   150  		// registered.
   151  		//
   152  		// Note that if there's built-in variables in pattern, this route will not be added
   153  		// automatically.
   154  		var (
   155  			isIndexMethod = strings.EqualFold(methodName, specialMethodNameIndex)
   156  			hasBuildInVar = gregex.IsMatchString(`\{\.\w+\}`, in.Pattern)
   157  			hashTwoParams = funcInfo.Type.NumIn() == 2
   158  		)
   159  		if isIndexMethod && !hasBuildInVar && !hashTwoParams {
   160  			p := gstr.PosRI(key, "/index")
   161  			k := key[0:p] + key[p+6:]
   162  			if len(k) == 0 || k[0] == '@' {
   163  				k = "/" + k
   164  			}
   165  			handlerMap[k] = &HandlerItem{
   166  				Name:       fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
   167  				Type:       HandlerTypeObject,
   168  				Info:       funcInfo,
   169  				InitFunc:   initFunc,
   170  				ShutFunc:   shutFunc,
   171  				Middleware: in.Middleware,
   172  				Source:     in.Source,
   173  			}
   174  		}
   175  	}
   176  	s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
   177  }
   178  
   179  type doBindObjectMethodInput struct {
   180  	Prefix     string
   181  	Pattern    string
   182  	Object     interface{}
   183  	Method     string
   184  	Middleware []HandlerFunc
   185  	Source     string
   186  }
   187  
   188  func (s *Server) doBindObjectMethod(ctx context.Context, in doBindObjectMethodInput) {
   189  	var (
   190  		handlerMap   = make(map[string]*HandlerItem)
   191  		reflectValue = reflect.ValueOf(in.Object)
   192  		reflectType  = reflectValue.Type()
   193  		initFunc     func(*Request)
   194  		shutFunc     func(*Request)
   195  	)
   196  	// If given `object` is not pointer, it then creates a temporary one,
   197  	// of which the value is `v`.
   198  	if reflectValue.Kind() == reflect.Struct {
   199  		newValue := reflect.New(reflectType)
   200  		newValue.Elem().Set(reflectValue)
   201  		reflectValue = newValue
   202  		reflectType = reflectValue.Type()
   203  	}
   204  	var (
   205  		structName  = reflectType.Elem().Name()
   206  		methodName  = strings.TrimSpace(in.Method)
   207  		methodValue = reflectValue.MethodByName(methodName)
   208  	)
   209  	if !methodValue.IsValid() {
   210  		s.Logger().Fatalf(ctx, "invalid method name: %s", methodName)
   211  		return
   212  	}
   213  	if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
   214  		initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
   215  	}
   216  	if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
   217  		shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
   218  	}
   219  	var (
   220  		pkgPath = reflectType.Elem().PkgPath()
   221  		pkgName = gfile.Basename(pkgPath)
   222  		objName = gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
   223  	)
   224  	if objName[0] == '*' {
   225  		objName = fmt.Sprintf(`(%s)`, objName)
   226  	}
   227  
   228  	funcInfo, err := s.checkAndCreateFuncInfo(methodValue.Interface(), pkgPath, objName, methodName)
   229  	if err != nil {
   230  		s.Logger().Fatalf(ctx, `%+v`, err)
   231  	}
   232  
   233  	key := s.mergeBuildInNameToPattern(in.Pattern, structName, methodName, false)
   234  	handlerMap[key] = &HandlerItem{
   235  		Name:       fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
   236  		Type:       HandlerTypeObject,
   237  		Info:       funcInfo,
   238  		InitFunc:   initFunc,
   239  		ShutFunc:   shutFunc,
   240  		Middleware: in.Middleware,
   241  		Source:     in.Source,
   242  	}
   243  
   244  	s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
   245  }
   246  
   247  func (s *Server) doBindObjectRest(ctx context.Context, in doBindObjectInput) {
   248  	var (
   249  		handlerMap   = make(map[string]*HandlerItem)
   250  		reflectValue = reflect.ValueOf(in.Object)
   251  		reflectType  = reflectValue.Type()
   252  		initFunc     func(*Request)
   253  		shutFunc     func(*Request)
   254  	)
   255  	// If given `object` is not pointer, it then creates a temporary one,
   256  	// of which the value is `v`.
   257  	if reflectValue.Kind() == reflect.Struct {
   258  		newValue := reflect.New(reflectType)
   259  		newValue.Elem().Set(reflectValue)
   260  		reflectValue = newValue
   261  		reflectType = reflectValue.Type()
   262  	}
   263  	structName := reflectType.Elem().Name()
   264  	if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
   265  		initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
   266  	}
   267  	if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
   268  		shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
   269  	}
   270  	pkgPath := reflectType.Elem().PkgPath()
   271  	for i := 0; i < reflectValue.NumMethod(); i++ {
   272  		methodName := reflectType.Method(i).Name
   273  		if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
   274  			continue
   275  		}
   276  		pkgName := gfile.Basename(pkgPath)
   277  		objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
   278  		if objName[0] == '*' {
   279  			objName = fmt.Sprintf(`(%s)`, objName)
   280  		}
   281  
   282  		funcInfo, err := s.checkAndCreateFuncInfo(
   283  			reflectValue.Method(i).Interface(),
   284  			pkgPath,
   285  			objName,
   286  			methodName,
   287  		)
   288  		if err != nil {
   289  			s.Logger().Fatalf(ctx, `%+v`, err)
   290  		}
   291  
   292  		key := s.mergeBuildInNameToPattern(methodName+":"+in.Pattern, structName, methodName, false)
   293  		handlerMap[key] = &HandlerItem{
   294  			Name:       fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName),
   295  			Type:       HandlerTypeObject,
   296  			Info:       funcInfo,
   297  			InitFunc:   initFunc,
   298  			ShutFunc:   shutFunc,
   299  			Middleware: in.Middleware,
   300  			Source:     in.Source,
   301  		}
   302  	}
   303  	s.bindHandlerByMap(ctx, in.Prefix, handlerMap)
   304  }