github.com/gogf/gf/v2@v2.7.4/net/ghttp/ghttp_server_router_group.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 "context" 11 "fmt" 12 "reflect" 13 14 "github.com/gogf/gf/v2/debug/gdebug" 15 "github.com/gogf/gf/v2/internal/consts" 16 "github.com/gogf/gf/v2/internal/reflection" 17 "github.com/gogf/gf/v2/text/gstr" 18 "github.com/gogf/gf/v2/util/gconv" 19 ) 20 21 type ( 22 // RouterGroup is a group wrapping multiple routes and middleware. 23 RouterGroup struct { 24 parent *RouterGroup // Parent group. 25 server *Server // Server. 26 domain *Domain // Domain. 27 prefix string // Prefix for sub-route. 28 middleware []HandlerFunc // Middleware array. 29 } 30 31 // preBindItem is item for lazy registering feature of router group. preBindItem is not really registered 32 // to server when route function of the group called but is lazily registered when server starts. 33 preBindItem struct { 34 group *RouterGroup 35 bindType string 36 pattern string 37 object interface{} // Can be handler, controller or object. 38 params []interface{} // Extra parameters for route registering depending on the type. 39 source string // Handler is a register at a certain source file path: line. 40 bound bool // Is this item bound to server? 41 } 42 ) 43 44 const ( 45 groupBindTypeHandler = "HANDLER" 46 groupBindTypeRest = "REST" 47 groupBindTypeHook = "HOOK" 48 groupBindTypeMiddleware = "MIDDLEWARE" 49 ) 50 51 var ( 52 preBindItems = make([]*preBindItem, 0, 64) 53 ) 54 55 // handlePreBindItems is called when server starts, which does really route registering to the server. 56 func (s *Server) handlePreBindItems(ctx context.Context) { 57 if len(preBindItems) == 0 { 58 return 59 } 60 for _, item := range preBindItems { 61 if item.bound { 62 continue 63 } 64 // Handle the items of current server. 65 if item.group.server != nil && item.group.server != s { 66 continue 67 } 68 if item.group.domain != nil && item.group.domain.server != s { 69 continue 70 } 71 item.group.doBindRoutersToServer(ctx, item) 72 item.bound = true 73 } 74 } 75 76 // Group creates and returns a RouterGroup object. 77 func (s *Server) Group(prefix string, groups ...func(group *RouterGroup)) *RouterGroup { 78 if len(prefix) > 0 && prefix[0] != '/' { 79 prefix = "/" + prefix 80 } 81 if prefix == "/" { 82 prefix = "" 83 } 84 group := &RouterGroup{ 85 server: s, 86 prefix: prefix, 87 } 88 if len(groups) > 0 { 89 for _, v := range groups { 90 v(group) 91 } 92 } 93 return group 94 } 95 96 // Group creates and returns a RouterGroup object, which is bound to a specified domain. 97 func (d *Domain) Group(prefix string, groups ...func(group *RouterGroup)) *RouterGroup { 98 if len(prefix) > 0 && prefix[0] != '/' { 99 prefix = "/" + prefix 100 } 101 if prefix == "/" { 102 prefix = "" 103 } 104 routerGroup := &RouterGroup{ 105 domain: d, 106 server: d.server, 107 prefix: prefix, 108 } 109 if len(groups) > 0 { 110 for _, nestedGroup := range groups { 111 nestedGroup(routerGroup) 112 } 113 } 114 return routerGroup 115 } 116 117 // Group creates and returns a subgroup of the current router group. 118 func (g *RouterGroup) Group(prefix string, groups ...func(group *RouterGroup)) *RouterGroup { 119 if prefix == "/" { 120 prefix = "" 121 } 122 group := &RouterGroup{ 123 parent: g, 124 server: g.server, 125 domain: g.domain, 126 prefix: prefix, 127 } 128 if len(g.middleware) > 0 { 129 group.middleware = make([]HandlerFunc, len(g.middleware)) 130 copy(group.middleware, g.middleware) 131 } 132 if len(groups) > 0 { 133 for _, v := range groups { 134 v(group) 135 } 136 } 137 return group 138 } 139 140 // Clone returns a new router group which is a clone of the current group. 141 func (g *RouterGroup) Clone() *RouterGroup { 142 newGroup := &RouterGroup{ 143 parent: g.parent, 144 server: g.server, 145 domain: g.domain, 146 prefix: g.prefix, 147 middleware: make([]HandlerFunc, len(g.middleware)), 148 } 149 copy(newGroup.middleware, g.middleware) 150 return newGroup 151 } 152 153 // Bind does batch route registering feature for a router group. 154 func (g *RouterGroup) Bind(handlerOrObject ...interface{}) *RouterGroup { 155 var ( 156 ctx = context.TODO() 157 group = g.Clone() 158 ) 159 for _, v := range handlerOrObject { 160 var ( 161 item = v 162 originValueAndKind = reflection.OriginValueAndKind(item) 163 ) 164 165 switch originValueAndKind.OriginKind { 166 case reflect.Func, reflect.Struct: 167 group = group.preBindToLocalArray( 168 groupBindTypeHandler, 169 "/", 170 item, 171 ) 172 173 default: 174 g.server.Logger().Fatalf( 175 ctx, "invalid bind parameter type: %v, should be route function or struct object", 176 originValueAndKind.InputValue.Type(), 177 ) 178 } 179 } 180 return group 181 } 182 183 // ALL register an http handler to give the route pattern and all http methods. 184 func (g *RouterGroup) ALL(pattern string, object interface{}, params ...interface{}) *RouterGroup { 185 return g.Clone().preBindToLocalArray( 186 groupBindTypeHandler, 187 defaultMethod+":"+pattern, 188 object, 189 params..., 190 ) 191 } 192 193 // ALLMap registers http handlers for http methods using map. 194 func (g *RouterGroup) ALLMap(m map[string]interface{}) { 195 for pattern, object := range m { 196 g.ALL(pattern, object) 197 } 198 } 199 200 // Map registers http handlers for http methods using map. 201 func (g *RouterGroup) Map(m map[string]interface{}) { 202 for pattern, object := range m { 203 g.preBindToLocalArray(groupBindTypeHandler, pattern, object) 204 } 205 } 206 207 // GET registers an http handler to give the route pattern and the http method: GET. 208 func (g *RouterGroup) GET(pattern string, object interface{}, params ...interface{}) *RouterGroup { 209 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "GET:"+pattern, object, params...) 210 } 211 212 // PUT registers an http handler to give the route pattern and the http method: PUT. 213 func (g *RouterGroup) PUT(pattern string, object interface{}, params ...interface{}) *RouterGroup { 214 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "PUT:"+pattern, object, params...) 215 } 216 217 // POST registers an http handler to give the route pattern and the http method: POST. 218 func (g *RouterGroup) POST(pattern string, object interface{}, params ...interface{}) *RouterGroup { 219 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "POST:"+pattern, object, params...) 220 } 221 222 // DELETE registers an http handler to give the route pattern and the http method: DELETE. 223 func (g *RouterGroup) DELETE(pattern string, object interface{}, params ...interface{}) *RouterGroup { 224 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "DELETE:"+pattern, object, params...) 225 } 226 227 // PATCH registers an http handler to give the route pattern and the http method: PATCH. 228 func (g *RouterGroup) PATCH(pattern string, object interface{}, params ...interface{}) *RouterGroup { 229 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "PATCH:"+pattern, object, params...) 230 } 231 232 // HEAD registers an http handler to give the route pattern and the http method: HEAD. 233 func (g *RouterGroup) HEAD(pattern string, object interface{}, params ...interface{}) *RouterGroup { 234 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "HEAD:"+pattern, object, params...) 235 } 236 237 // CONNECT registers an http handler to give the route pattern and the http method: CONNECT. 238 func (g *RouterGroup) CONNECT(pattern string, object interface{}, params ...interface{}) *RouterGroup { 239 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "CONNECT:"+pattern, object, params...) 240 } 241 242 // OPTIONS register an http handler to give the route pattern and the http method: OPTIONS. 243 func (g *RouterGroup) OPTIONS(pattern string, object interface{}, params ...interface{}) *RouterGroup { 244 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "OPTIONS:"+pattern, object, params...) 245 } 246 247 // TRACE registers an http handler to give the route pattern and the http method: TRACE. 248 func (g *RouterGroup) TRACE(pattern string, object interface{}, params ...interface{}) *RouterGroup { 249 return g.Clone().preBindToLocalArray(groupBindTypeHandler, "TRACE:"+pattern, object, params...) 250 } 251 252 // REST registers an http handler to give the route pattern according to REST rule. 253 func (g *RouterGroup) REST(pattern string, object interface{}) *RouterGroup { 254 return g.Clone().preBindToLocalArray(groupBindTypeRest, pattern, object) 255 } 256 257 // Hook registers a hook to given route pattern. 258 func (g *RouterGroup) Hook(pattern string, hook HookName, handler HandlerFunc) *RouterGroup { 259 return g.Clone().preBindToLocalArray(groupBindTypeHandler, pattern, handler, hook) 260 } 261 262 // Middleware binds one or more middleware to the router group. 263 func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup { 264 g.middleware = append(g.middleware, handlers...) 265 return g 266 } 267 268 // preBindToLocalArray adds the route registering parameters to an internal variable array for lazily registering feature. 269 func (g *RouterGroup) preBindToLocalArray(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup { 270 _, file, line := gdebug.CallerWithFilter([]string{consts.StackFilterKeyForGoFrame}) 271 preBindItems = append(preBindItems, &preBindItem{ 272 group: g, 273 bindType: bindType, 274 pattern: pattern, 275 object: object, 276 params: params, 277 source: fmt.Sprintf(`%s:%d`, file, line), 278 }) 279 return g 280 } 281 282 // getPrefix returns the route prefix of the group, which recursively retrieves its parent's prefix. 283 func (g *RouterGroup) getPrefix() string { 284 prefix := g.prefix 285 parent := g.parent 286 for parent != nil { 287 prefix = parent.prefix + prefix 288 parent = parent.parent 289 } 290 return prefix 291 } 292 293 // doBindRoutersToServer does really register for the group. 294 func (g *RouterGroup) doBindRoutersToServer(ctx context.Context, item *preBindItem) *RouterGroup { 295 var ( 296 bindType = item.bindType 297 pattern = item.pattern 298 object = item.object 299 params = item.params 300 source = item.source 301 ) 302 prefix := g.getPrefix() 303 // Route check. 304 if len(prefix) > 0 { 305 domain, method, path, err := g.server.parsePattern(pattern) 306 if err != nil { 307 g.server.Logger().Fatalf(ctx, "invalid route pattern: %s", pattern) 308 } 309 // If there is already a domain, unset the domain field in the pattern. 310 if g.domain != nil { 311 domain = "" 312 } 313 if bindType == groupBindTypeRest { 314 pattern = path 315 } else { 316 pattern = g.server.serveHandlerKey( 317 method, path, domain, 318 ) 319 } 320 } 321 // Filter repeated char '/'. 322 pattern = gstr.Replace(pattern, "//", "/") 323 324 // Convert params to a string array. 325 extras := gconv.Strings(params) 326 327 // Check whether it's a hook handler. 328 if _, ok := object.(HandlerFunc); ok && len(extras) > 0 { 329 bindType = groupBindTypeHook 330 } 331 switch bindType { 332 case groupBindTypeHandler: 333 if reflect.ValueOf(object).Kind() == reflect.Func { 334 funcInfo, err := g.server.checkAndCreateFuncInfo(object, "", "", "") 335 if err != nil { 336 g.server.Logger().Fatal(ctx, err.Error()) 337 return g 338 } 339 in := doBindHandlerInput{ 340 Prefix: prefix, 341 Pattern: pattern, 342 FuncInfo: funcInfo, 343 Middleware: g.middleware, 344 Source: source, 345 } 346 if g.domain != nil { 347 g.domain.doBindHandler(ctx, in) 348 } else { 349 g.server.doBindHandler(ctx, in) 350 } 351 } else { 352 if len(extras) > 0 { 353 if gstr.Contains(extras[0], ",") { 354 in := doBindObjectInput{ 355 Prefix: prefix, 356 Pattern: pattern, 357 Object: object, 358 Method: extras[0], 359 Middleware: g.middleware, 360 Source: source, 361 } 362 if g.domain != nil { 363 g.domain.doBindObject(ctx, in) 364 } else { 365 g.server.doBindObject(ctx, in) 366 } 367 } else { 368 in := doBindObjectMethodInput{ 369 Prefix: prefix, 370 Pattern: pattern, 371 Object: object, 372 Method: extras[0], 373 Middleware: g.middleware, 374 Source: source, 375 } 376 if g.domain != nil { 377 g.domain.doBindObjectMethod(ctx, in) 378 } else { 379 g.server.doBindObjectMethod(ctx, in) 380 } 381 } 382 } else { 383 in := doBindObjectInput{ 384 Prefix: prefix, 385 Pattern: pattern, 386 Object: object, 387 Method: "", 388 Middleware: g.middleware, 389 Source: source, 390 } 391 // Finally, it treats the `object` as the Object registering type. 392 if g.domain != nil { 393 g.domain.doBindObject(ctx, in) 394 } else { 395 g.server.doBindObject(ctx, in) 396 } 397 } 398 } 399 400 case groupBindTypeRest: 401 in := doBindObjectInput{ 402 Prefix: prefix, 403 Pattern: pattern, 404 Object: object, 405 Method: "", 406 Middleware: g.middleware, 407 Source: source, 408 } 409 if g.domain != nil { 410 g.domain.doBindObjectRest(ctx, in) 411 } else { 412 g.server.doBindObjectRest(ctx, in) 413 } 414 415 case groupBindTypeHook: 416 if handler, ok := object.(HandlerFunc); ok { 417 in := doBindHookHandlerInput{ 418 Prefix: prefix, 419 Pattern: pattern, 420 HookName: HookName(extras[0]), 421 Handler: handler, 422 Source: source, 423 } 424 if g.domain != nil { 425 g.domain.doBindHookHandler(ctx, in) 426 } else { 427 g.server.doBindHookHandler(ctx, in) 428 } 429 } else { 430 g.server.Logger().Fatalf(ctx, "invalid hook handler for pattern: %s", pattern) 431 } 432 } 433 return g 434 }