github.com/gogf/gf/v2@v2.7.4/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/gogf/gf. 6 7 package ghttp 8 9 import ( 10 "bytes" 11 "context" 12 "reflect" 13 "strings" 14 15 "github.com/gogf/gf/v2/errors/gcode" 16 "github.com/gogf/gf/v2/errors/gerror" 17 "github.com/gogf/gf/v2/os/gstructs" 18 "github.com/gogf/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 }