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  }