github.com/wangyougui/gf/v2@v2.6.5/net/ghttp/ghttp_server_service_handler.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  	"bytes"
    11  	"context"
    12  	"reflect"
    13  	"strings"
    14  
    15  	"github.com/wangyougui/gf/v2/errors/gcode"
    16  	"github.com/wangyougui/gf/v2/errors/gerror"
    17  	"github.com/wangyougui/gf/v2/os/gstructs"
    18  	"github.com/wangyougui/gf/v2/text/gstr"
    19  )
    20  
    21  // BindHandler registers a handler function to server with a given pattern.
    22  //
    23  // Note that the parameter `handler` can be type of:
    24  // 1. func(*ghttp.Request)
    25  // 2. func(context.Context, BizRequest)(BizResponse, error)
    26  func (s *Server) BindHandler(pattern string, handler interface{}) {
    27  	var ctx = context.TODO()
    28  	funcInfo, err := s.checkAndCreateFuncInfo(handler, "", "", "")
    29  	if err != nil {
    30  		s.Logger().Fatalf(ctx, `%+v`, err)
    31  	}
    32  	s.doBindHandler(ctx, doBindHandlerInput{
    33  		Prefix:     "",
    34  		Pattern:    pattern,
    35  		FuncInfo:   funcInfo,
    36  		Middleware: nil,
    37  		Source:     "",
    38  	})
    39  }
    40  
    41  type doBindHandlerInput struct {
    42  	Prefix     string
    43  	Pattern    string
    44  	FuncInfo   handlerFuncInfo
    45  	Middleware []HandlerFunc
    46  	Source     string
    47  }
    48  
    49  // doBindHandler registers a handler function to server with given pattern.
    50  //
    51  // The parameter `pattern` is like:
    52  // /user/list, put:/user, delete:/user, post:/user@goframe.org
    53  func (s *Server) doBindHandler(ctx context.Context, in doBindHandlerInput) {
    54  	s.setHandler(ctx, setHandlerInput{
    55  		Prefix:  in.Prefix,
    56  		Pattern: in.Pattern,
    57  		HandlerItem: &HandlerItem{
    58  			Type:       HandlerTypeHandler,
    59  			Info:       in.FuncInfo,
    60  			Middleware: in.Middleware,
    61  			Source:     in.Source,
    62  		},
    63  	})
    64  }
    65  
    66  // bindHandlerByMap registers handlers to server using map.
    67  func (s *Server) bindHandlerByMap(ctx context.Context, prefix string, m map[string]*HandlerItem) {
    68  	for pattern, handler := range m {
    69  		s.setHandler(ctx, setHandlerInput{
    70  			Prefix:      prefix,
    71  			Pattern:     pattern,
    72  			HandlerItem: handler,
    73  		})
    74  	}
    75  }
    76  
    77  // mergeBuildInNameToPattern merges build-in names into the pattern according to the following
    78  // rules, and the built-in names are named like "{.xxx}".
    79  // Rule 1: The URI in pattern contains the {.struct} keyword, it then replaces the keyword with the struct name;
    80  // Rule 2: The URI in pattern contains the {.method} keyword, it then replaces the keyword with the method name;
    81  // Rule 2: If Rule 1 is not met, it then adds the method name directly to the URI in the pattern;
    82  //
    83  // The parameter `allowAppend` specifies whether allowing appending method name to the tail of pattern.
    84  func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string, allowAppend bool) string {
    85  	structName = s.nameToUri(structName)
    86  	methodName = s.nameToUri(methodName)
    87  	pattern = strings.ReplaceAll(pattern, "{.struct}", structName)
    88  	if strings.Contains(pattern, "{.method}") {
    89  		return strings.ReplaceAll(pattern, "{.method}", methodName)
    90  	}
    91  	if !allowAppend {
    92  		return pattern
    93  	}
    94  	// Check domain parameter.
    95  	var (
    96  		array = strings.Split(pattern, "@")
    97  		uri   = strings.TrimRight(array[0], "/") + "/" + methodName
    98  	)
    99  	// Append the domain parameter to URI.
   100  	if len(array) > 1 {
   101  		return uri + "@" + array[1]
   102  	}
   103  	return uri
   104  }
   105  
   106  // nameToUri converts the given name to the URL format using the following rules:
   107  // Rule 0: Convert all method names to lowercase, add char '-' between words.
   108  // Rule 1: Do not convert the method name, construct the URI with the original method name.
   109  // Rule 2: Convert all method names to lowercase, no connecting symbols between words.
   110  // Rule 3: Use camel case naming.
   111  func (s *Server) nameToUri(name string) string {
   112  	switch s.config.NameToUriType {
   113  	case UriTypeFullName:
   114  		return name
   115  
   116  	case UriTypeAllLower:
   117  		return strings.ToLower(name)
   118  
   119  	case UriTypeCamel:
   120  		part := bytes.NewBuffer(nil)
   121  		if gstr.IsLetterUpper(name[0]) {
   122  			part.WriteByte(name[0] + 32)
   123  		} else {
   124  			part.WriteByte(name[0])
   125  		}
   126  		part.WriteString(name[1:])
   127  		return part.String()
   128  
   129  	case UriTypeDefault:
   130  		fallthrough
   131  
   132  	default:
   133  		part := bytes.NewBuffer(nil)
   134  		for i := 0; i < len(name); i++ {
   135  			if i > 0 && gstr.IsLetterUpper(name[i]) {
   136  				part.WriteByte('-')
   137  			}
   138  			if gstr.IsLetterUpper(name[i]) {
   139  				part.WriteByte(name[i] + 32)
   140  			} else {
   141  				part.WriteByte(name[i])
   142  			}
   143  		}
   144  		return part.String()
   145  	}
   146  }
   147  
   148  func (s *Server) checkAndCreateFuncInfo(
   149  	f interface{}, pkgPath, structName, methodName string,
   150  ) (funcInfo handlerFuncInfo, err error) {
   151  	funcInfo = handlerFuncInfo{
   152  		Type:  reflect.TypeOf(f),
   153  		Value: reflect.ValueOf(f),
   154  	}
   155  	if handlerFunc, ok := f.(HandlerFunc); ok {
   156  		funcInfo.Func = handlerFunc
   157  		return
   158  	}
   159  
   160  	var (
   161  		reflectType    = funcInfo.Type
   162  		inputObject    reflect.Value
   163  		inputObjectPtr interface{}
   164  	)
   165  	if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
   166  		if pkgPath != "" {
   167  			err = gerror.NewCodef(
   168  				gcode.CodeInvalidParameter,
   169  				`invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`,
   170  				pkgPath, structName, methodName, reflectType.String(),
   171  			)
   172  		} else {
   173  			err = gerror.NewCodef(
   174  				gcode.CodeInvalidParameter,
   175  				`invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`,
   176  				reflectType.String(),
   177  			)
   178  		}
   179  		return
   180  	}
   181  
   182  	if !reflectType.In(0).Implements(reflect.TypeOf((*context.Context)(nil)).Elem()) {
   183  		err = gerror.NewCodef(
   184  			gcode.CodeInvalidParameter,
   185  			`invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`,
   186  			reflectType.String(),
   187  		)
   188  		return
   189  	}
   190  
   191  	if !reflectType.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
   192  		err = gerror.NewCodef(
   193  			gcode.CodeInvalidParameter,
   194  			`invalid handler: defined as "%s", but the last output parameter should be type of "error"`,
   195  			reflectType.String(),
   196  		)
   197  		return
   198  	}
   199  
   200  	if reflectType.In(1).Kind() != reflect.Ptr ||
   201  		(reflectType.In(1).Kind() == reflect.Ptr && reflectType.In(1).Elem().Kind() != reflect.Struct) {
   202  		err = gerror.NewCodef(
   203  			gcode.CodeInvalidParameter,
   204  			`invalid handler: defined as "%s", but the second input parameter should be type of pointer to struct like "*BizReq"`,
   205  			reflectType.String(),
   206  		)
   207  		return
   208  	}
   209  
   210  	// Do not enable this logic, as many users are already using none struct pointer type
   211  	// as the first output parameter.
   212  	/*
   213  		if reflectType.Out(0).Kind() != reflect.Ptr ||
   214  			(reflectType.Out(0).Kind() == reflect.Ptr && reflectType.Out(0).Elem().Kind() != reflect.Struct) {
   215  			err = gerror.NewCodef(
   216  				gcode.CodeInvalidParameter,
   217  				`invalid handler: defined as "%s", but the first output parameter should be type of pointer to struct like "*BizRes"`,
   218  				reflectType.String(),
   219  			)
   220  			return
   221  		}
   222  	*/
   223  
   224  	// The request struct should be named as `xxxReq`.
   225  	reqStructName := trimGeneric(reflectType.In(1).String())
   226  	if !gstr.HasSuffix(reqStructName, `Req`) {
   227  		err = gerror.NewCodef(
   228  			gcode.CodeInvalidParameter,
   229  			`invalid struct naming for request: defined as "%s", but it should be named with "Req" suffix like "XxxReq"`,
   230  			reqStructName,
   231  		)
   232  		return
   233  	}
   234  
   235  	// The response struct should be named as `xxxRes`.
   236  	resStructName := trimGeneric(reflectType.Out(0).String())
   237  	if !gstr.HasSuffix(resStructName, `Res`) {
   238  		err = gerror.NewCodef(
   239  			gcode.CodeInvalidParameter,
   240  			`invalid struct naming for response: defined as "%s", but it should be named with "Res" suffix like "XxxRes"`,
   241  			resStructName,
   242  		)
   243  		return
   244  	}
   245  
   246  	funcInfo.IsStrictRoute = true
   247  
   248  	inputObject = reflect.New(funcInfo.Type.In(1).Elem())
   249  	inputObjectPtr = inputObject.Interface()
   250  
   251  	// It retrieves and returns the request struct fields.
   252  	fields, err := gstructs.Fields(gstructs.FieldsInput{
   253  		Pointer:         inputObjectPtr,
   254  		RecursiveOption: gstructs.RecursiveOptionEmbedded,
   255  	})
   256  	if err != nil {
   257  		return funcInfo, err
   258  	}
   259  	funcInfo.ReqStructFields = fields
   260  	funcInfo.Func = createRouterFunc(funcInfo)
   261  	return
   262  }
   263  
   264  func createRouterFunc(funcInfo handlerFuncInfo) func(r *Request) {
   265  	return func(r *Request) {
   266  		var (
   267  			ok          bool
   268  			err         error
   269  			inputValues = []reflect.Value{
   270  				reflect.ValueOf(r.Context()),
   271  			}
   272  		)
   273  		if funcInfo.Type.NumIn() == 2 {
   274  			var inputObject reflect.Value
   275  			if funcInfo.Type.In(1).Kind() == reflect.Ptr {
   276  				inputObject = reflect.New(funcInfo.Type.In(1).Elem())
   277  				r.error = r.Parse(inputObject.Interface())
   278  			} else {
   279  				inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem()
   280  				r.error = r.Parse(inputObject.Addr().Interface())
   281  			}
   282  			if r.error != nil {
   283  				return
   284  			}
   285  			inputValues = append(inputValues, inputObject)
   286  		}
   287  		// Call handler with dynamic created parameter values.
   288  		results := funcInfo.Value.Call(inputValues)
   289  		switch len(results) {
   290  		case 1:
   291  			if !results[0].IsNil() {
   292  				if err, ok = results[0].Interface().(error); ok {
   293  					r.error = err
   294  				}
   295  			}
   296  
   297  		case 2:
   298  			r.handlerResponse = results[0].Interface()
   299  			if !results[1].IsNil() {
   300  				if err, ok = results[1].Interface().(error); ok {
   301  					r.error = err
   302  				}
   303  			}
   304  		}
   305  	}
   306  }
   307  
   308  // trimGeneric removes type definitions string from response type name if generic
   309  func trimGeneric(structName string) string {
   310  	var (
   311  		leftBraceIndex  = strings.LastIndex(structName, "[") // for generic, it is faster to start at the end than at the beginning
   312  		rightBraceIndex = strings.LastIndex(structName, "]")
   313  	)
   314  	if leftBraceIndex == -1 || rightBraceIndex == -1 {
   315  		// not found '[' or ']'
   316  		return structName
   317  	} else if leftBraceIndex+1 == rightBraceIndex {
   318  		// may be a slice, because generic is '[X]', not '[]'
   319  		// to be compatible with bad return parameter type: []XxxRes
   320  		return structName
   321  	}
   322  	return structName[:leftBraceIndex]
   323  }