github.com/volts-dev/volts@v0.0.0-20240120094013-5e9c65924106/router/group.go (about)

     1  package router
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	_path "path"
     8  	"path/filepath"
     9  	"reflect"
    10  	"runtime"
    11  	"strings"
    12  	"sync"
    13  	"unicode"
    14  	"unicode/utf8"
    15  
    16  	"github.com/volts-dev/utils"
    17  	"github.com/volts-dev/volts/broker"
    18  	"github.com/volts-dev/volts/config"
    19  )
    20  
    21  type (
    22  	IString interface {
    23  		String() string
    24  	}
    25  
    26  	// [scheme:][//[userinfo@]host][/[path]/controller.action][?query][#fragment]
    27  	TUrl struct {
    28  		Scheme     string
    29  		Opaque     string // encoded opaque data
    30  		Host       string // host or host:port
    31  		Path       string // path (relative paths may omit leading slash)
    32  		Controller string
    33  		Action     string
    34  		//RawPath    string    // encoded path hint (see EscapedPath method); added in Go 1.5
    35  		//ForceQuery bool      // append a query ('?') even if RawQuery is empty; added in Go 1.7
    36  		//RawQuery   string    // encoded query values, without '?'
    37  		//Fragment   string    // fragment for references, without '#'
    38  	}
    39  
    40  	// 非volts通用接口
    41  	IGroup interface {
    42  		String() string
    43  		// 返回Module所有Routes 理论上只需被调用一次
    44  		GetRoutes() *TTree
    45  		GetSubscribers() map[ISubscriber][]broker.ISubscriber
    46  		GetPath() string
    47  		GetFilePath() string
    48  		GetTemplateVar() map[string]interface{}
    49  		IsService() bool
    50  	}
    51  
    52  	// 服务模块 每个服务代表一个对象
    53  	TGroup struct {
    54  		sync.RWMutex
    55  		*TemplateVar
    56  		config     *GroupConfig
    57  		middleware *TMiddlewareManager
    58  		tree       *TTree
    59  		rcvr       reflect.Value // receiver of methods for the module
    60  		typ        reflect.Type  // type of the receiver
    61  		path       string        // URL 路径
    62  		//modulePath string // 当前模块文件夹路径
    63  		domain string // 子域名用于区分不同域名不同路由
    64  
    65  		subscribers map[ISubscriber][]broker.ISubscriber
    66  		Metadata    map[string]string // 存储Group数据
    67  	}
    68  
    69  	TemplateVar struct {
    70  		templateVar map[string]interface{} // TODO 全局变量. 需改进
    71  	}
    72  )
    73  
    74  /*
    75  	"""Return the path of the given module.
    76  
    77  Search the addons paths and return the first path where the given
    78  module is found. If downloaded is True, return the default addons
    79  path if nothing else is found.
    80  
    81  """
    82  */
    83  func GetModulePath(module string, downloaded bool, display_warning bool) (res string) {
    84  
    85  	// initialize_sys_path()
    86  	// for adp in ad_paths:
    87  	//      if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
    88  	//         return opj(adp, module)
    89  	res = filepath.Join(config.AppPath, MODULE_DIR)
    90  	//if _, err := os.Stat(res); err == nil {
    91  	//	return res
    92  	//}
    93  	return
    94  
    95  	// if downloaded:
    96  	//    return opj(tools.config.addons_data_dir, module)
    97  	//if display_warning {
    98  	//	logger.Warnf(`module %s: module not found`, module)
    99  	//}
   100  
   101  	//return ""
   102  }
   103  
   104  /*
   105     """Return the full path of a resource of the given module.
   106  
   107     :param module: module name
   108     :param list(str) args: resource path components within module
   109  
   110     :rtype: str
   111     :return: absolute path to the resource
   112  
   113     TODO make it available inside on osv object (self.get_resource_path)
   114     """*/
   115  
   116  func GetResourcePath(module_src_path string) (res string) {
   117  	//filepath.SplitList(module_src_path)
   118  	mod_path := GetModulePath("", false, true)
   119  
   120  	res = filepath.Join(mod_path, module_src_path)
   121  
   122  	if _, err := os.Stat(res); err == nil {
   123  		return
   124  	}
   125  
   126  	/*
   127  	   if  res!=="" return False
   128  	   resource_path = opj(mod_path, *args)
   129  	   if os.path.isdir(mod_path):
   130  	       # the module is a directory - ignore zip behavior
   131  	       if os.path.exists(resource_path):
   132  	           return resource_path
   133  	*/
   134  	return ""
   135  }
   136  
   137  func newTemplateVar() *TemplateVar {
   138  	return &TemplateVar{
   139  		templateVar: make(map[string]interface{}),
   140  	}
   141  }
   142  
   143  // set the var of the template
   144  func (self *TemplateVar) SetTemplateVar(key string, value interface{}) {
   145  	self.templateVar[key] = value
   146  }
   147  
   148  // remove the var from the template
   149  func (self *TemplateVar) DelTemplateVar(key string) {
   150  	delete(self.templateVar, key)
   151  }
   152  
   153  func (self *TemplateVar) GetTemplateVar() map[string]interface{} {
   154  	return self.templateVar
   155  }
   156  
   157  func isExported(name string) bool {
   158  	rune, _ := utf8.DecodeRuneInString(name)
   159  	return unicode.IsUpper(rune)
   160  }
   161  
   162  func isExportedOrBuiltinType(t reflect.Type) bool {
   163  	for t.Kind() == reflect.Ptr {
   164  		t = t.Elem()
   165  	}
   166  	// PkgPath will be non-empty even for an exported type,
   167  	// so we need to check the type name as well.
   168  	return isExported(t.Name()) || t.PkgPath() == ""
   169  }
   170  
   171  // get current source codes file path without file name
   172  func curFilePath(skip int) (string, string) {
   173  	/* just for dbg
   174  	s := 0
   175  	for {
   176  		if pc, file, _, ok := runtime.Caller(s); ok {
   177  			// Note that the test line may be different on
   178  			// distinct calls for the same test.  Showing
   179  			// the "internal" line is helpful when debugging.
   180  			log.Dbg(config.AppPath, pc, " ", file, s)
   181  		} else {
   182  			break
   183  		}
   184  		s += 1
   185  	}*/
   186  	_, file, _, _ := runtime.Caller(skip)
   187  	filePath, _ := _path.Split(file)
   188  	pkgName := filepath.Base(filePath) // TODO 过滤验证文件夹名称
   189  	// 过滤由router创建的组群
   190  	if pkgName == "router" {
   191  		return config.AppPath, ""
   192  	}
   193  
   194  	filePath = config.AppPath + "/" + MODULE_DIR + "/" + pkgName
   195  	return filePath, pkgName
   196  }
   197  
   198  // new a module
   199  // default url path :/{mod_name}
   200  // default file path :/{mod_name}/{static}/
   201  // default tmpl path :/{mod_name{/{tmpl}/
   202  func NewGroup(opts ...GroupOption) *TGroup {
   203  	cfg := &GroupConfig{}
   204  
   205  	for _, opt := range opts {
   206  		opt(cfg)
   207  	}
   208  
   209  	// 获取路径文件夹名称
   210  	filePath, name := curFilePath(2)
   211  	if cfg.Name == "" {
   212  		cfg.Name = name
   213  	}
   214  	if cfg.FilePath == "" {
   215  		cfg.FilePath = filePath
   216  	}
   217  
   218  	grp := &TGroup{
   219  		config:      cfg,
   220  		TemplateVar: newTemplateVar(),
   221  		tree:        NewRouteTree(WithIgnoreCase()),
   222  		path:        _path.Join("/", cfg.Name),
   223  		Metadata:    map[string]string{
   224  			//"name": cfg.Name,
   225  		},
   226  	}
   227  
   228  	// init router tree
   229  	grp.SetStatic("/static")
   230  	return grp
   231  }
   232  
   233  func (self *TGroup) String() string {
   234  	return self.config.Name
   235  }
   236  
   237  func (self *TGroup) Name() string {
   238  	return self.config.Name
   239  }
   240  
   241  func (self *TGroup) Config() *GroupConfig {
   242  	return self.config
   243  }
   244  
   245  func (self *TGroup) GetRoutes() *TTree {
   246  	return self.tree
   247  }
   248  
   249  func (self *TGroup) GetSubscribers() map[ISubscriber][]broker.ISubscriber {
   250  	return self.subscribers
   251  }
   252  
   253  // the URL path
   254  func (self *TGroup) GetPath() string {
   255  	return self.path
   256  }
   257  
   258  // 获取组的绝对路径
   259  func (self *TGroup) GetFilePath() string {
   260  	return self.config.FilePath
   261  }
   262  
   263  // 获取组的链接路径
   264  func (self *TGroup) SetPath(path string) {
   265  	self.path = path
   266  }
   267  
   268  func (self *TGroup) SetFilePath(path string) {
   269  	self.config.FilePath = path
   270  }
   271  
   272  // Static serves files from the given file system root.
   273  // Internally a http.FileServer is used, therefore http.NotFound is used instead
   274  // of the Router's NotFound handler.
   275  // To use the operating system's file system implementation,
   276  // use :
   277  //
   278  //	router.Static("/static", "/var/www")
   279  func (self *TGroup) SetStatic(relativePath string, root ...string) {
   280  	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
   281  		panic("URL parameters can not be used when serving a static folder")
   282  	}
   283  
   284  	groupFilepath := ""
   285  	if len(root) > 0 {
   286  		// 如果定义固定路径
   287  		groupFilepath = root[0]
   288  	} else {
   289  		// 非固定路径使用组所在路径+URL路径
   290  		groupFilepath = _path.Join(self.config.FilePath, relativePath)
   291  	}
   292  
   293  	urlPattern := _path.Join(self.path, relativePath)
   294  	absolutePath := _path.Join(self.config.PathPrefix, urlPattern) // the url path
   295  	handler := staticHandler(absolutePath, groupFilepath)
   296  	// 路由路径
   297  	fullRoutePattern := _path.Join(self.config.PathPrefix, urlPattern, fmt.Sprintf("/%s:filepath%s", string(LBracket), string(RBracket)))
   298  	self.addRoute(Before, LocalHandler, []string{"GET", "HEAD"}, &TUrl{Path: fullRoutePattern}, []any{handler})
   299  	// 模版变量
   300  	self.SetTemplateVar(relativePath[1:], urlPattern) // set the template var value
   301  }
   302  
   303  // StaticFile registers a single route in order to serve a single file of the local filesystem.
   304  // router.StaticFile("favicon.ico", "./resources/favicon.ico")
   305  func (self *TGroup) StaticFile(relativePath, filepath string) {
   306  	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
   307  		panic("URL parameters can not be used when serving a static file")
   308  	}
   309  	handler := func(c *THttpContext) {
   310  		c.ServeFile(filepath)
   311  	}
   312  
   313  	self.Url("GET", relativePath, handler)
   314  }
   315  
   316  // !NOTE! RPC 或者 HTTP 不适用同一Module注册路由
   317  /*
   318  Add route with method
   319  HTTP: "GET/POST/DELETE/PUT/HEAD/OPTIONS/REST"
   320  RPC: "CONNECT"
   321  
   322  Match rules
   323  Base: <type:name> if difine type than the route only match the string same to the type
   324  Example:
   325  		<string:id> 	only match "abc"
   326  		   <int:id> 	only match number "123"
   327  		      <:id> 	could match all kind of type with name id
   328  
   329  		'//' -- 生成"/abc/"而非"/abc"
   330  		'/web/<string:id>',
   331  		'/web/<string:id>/<string:name>',
   332  		'/web/<int:id>',
   333  		'/web/<int:id>/<string:name>',
   334  		'/web/<int:id>-<string:unique>',
   335  		'/web/<int:id>-<string:unique>/<string:name>',
   336  		'/web/<string:model>/<int:id>/<string:field>',
   337  		'/web/<string:model>/<int:id>/<string:field>/<string:name>'
   338  		'/web/*'
   339  
   340  for details please read tree.go
   341  */
   342  // @handlers 第一个将会被用于主要控制器其他将被归为中间件
   343  // @method:
   344  //			API:会映射所有对象符合要求的控制器作为API发布
   345  //			REST:会映射所有对象符合要求的create, read, update, and delete控制器作为Restful发布
   346  func (self *TGroup) Url(method string, path string, handlers ...any) *route {
   347  	if self.config.PathPrefix != "" && path == "//" {
   348  		// 生成"/abc/"而非"/abc"
   349  		path = _path.Join(self.config.PathPrefix, "") + "/"
   350  	} else {
   351  		path = _path.Join(self.config.PathPrefix, path)
   352  	}
   353  
   354  	method = strings.ToUpper(method)
   355  	switch method {
   356  	case "GET":
   357  		return self.addRoute(Normal, LocalHandler, []string{"GET", "HEAD"}, &TUrl{Path: path}, handlers[0:1], handlers[1:]...)
   358  	case "POST", "PUT", "HEAD", "OPTIONS", "TRACE", "PATCH", "DELETE":
   359  		return self.addRoute(Normal, LocalHandler, []string{method}, &TUrl{Path: path}, handlers[0:1], handlers[1:]...)
   360  	case "CONNECT": // RPC or WS
   361  		return self.addRoute(Normal, LocalHandler, []string{method}, &TUrl{Path: path}, handlers[0:1], handlers[1:]...)
   362  	case "REST", "RPC":
   363  		return self.addRoute(Normal, LocalHandler, []string{method}, &TUrl{Path: path}, handlers[0:1], handlers[1:]...)
   364  	default:
   365  		log.Fatalf("the params in Module.Url() %v:%v is invaild", method, path)
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  // 新建订阅对象
   372  func (h *TGroup) NewSubscriber(topic string, handler interface{}, opts ...SubscriberOption) ISubscriber {
   373  	return newSubscriber(topic, handler, opts...)
   374  }
   375  
   376  // 订阅对象
   377  func (self *TGroup) Subscribe(subscriber ISubscriber) error {
   378  	if len(subscriber.Handlers()) == 0 {
   379  		return fmt.Errorf("invalid subscriber: no handler functions")
   380  	}
   381  
   382  	if err := validateSubscriber(subscriber); err != nil {
   383  		return err
   384  	}
   385  
   386  	self.Lock()
   387  	defer self.Unlock()
   388  	_, ok := self.subscribers[subscriber]
   389  	if ok {
   390  		return fmt.Errorf("subscriber %v already exists", self)
   391  	}
   392  	self.subscribers[subscriber] = nil
   393  	return nil
   394  }
   395  
   396  /*
   397  pos: true 为插入Before 反之After
   398  */
   399  func (self *TGroup) addRoute(position RoutePosition, hanadlerType HandlerType, methods []string, url *TUrl, handlers []any, mids ...any) *route {
   400  	// check vaild
   401  	if hanadlerType != ProxyHandler && len(handlers) == 0 {
   402  		panic("the route must binding a controller!")
   403  	}
   404  
   405  	var hd *handler
   406  	h := handlers[0]
   407  	switch v := h.(type) {
   408  	case func(*TRpcContext):
   409  		hd = generateHandler(hanadlerType, RpcHandler, handlers, mids, url, nil)
   410  	case func(*THttpContext):
   411  		hd = generateHandler(hanadlerType, HttpHandler, handlers, mids, url, nil)
   412  	case func(http.ResponseWriter, *http.Request):
   413  		hd = generateHandler(hanadlerType, HttpHandler, []interface{}{WrapFn(v)}, mids, url, nil)
   414  	case http.HandlerFunc:
   415  		hd = generateHandler(hanadlerType, HttpHandler, []interface{}{WrapFn(v)}, mids, url, nil)
   416  	case http.Handler:
   417  		hd = generateHandler(hanadlerType, HttpHandler, []interface{}{WrapHd(v)}, mids, url, nil)
   418  	default:
   419  		// init Value and Type
   420  		ctrlValue, ok := h.(reflect.Value)
   421  		if !ok {
   422  			ctrlValue = reflect.ValueOf(h)
   423  		}
   424  		ctrlType := ctrlValue.Type()
   425  
   426  		kind := ctrlType.Kind()
   427  		switch kind {
   428  		case reflect.Struct, reflect.Ptr:
   429  			// transfer prt to struct
   430  			if kind == reflect.Ptr {
   431  				ctrlValue = ctrlValue.Elem()
   432  				ctrlType = ctrlType.Elem()
   433  			}
   434  
   435  			// 获取控制器名称
   436  			var objName string
   437  			if v, ok := h.(IString); ok {
   438  				objName = v.String()
   439  			} else {
   440  				objName = utils.DotCasedName(utils.Obj2Name(h))
   441  			}
   442  
   443  			var name string
   444  			var method reflect.Value
   445  			var contextType reflect.Type
   446  			useREST := utils.InStrings("REST", methods...) > -1
   447  			useRPC := utils.InStrings("RPC", methods...) > -1 || utils.InStrings("CONNECT", methods...) > -1
   448  			for i := 0; i < ctrlType.NumMethod(); i++ {
   449  				// get the method information from the ctrl Type
   450  				name = ctrlType.Method(i).Name
   451  				method = ctrlType.Method(i).Func
   452  
   453  				// 忽略非handler方法
   454  				if method.Type().NumIn() <= 1 {
   455  					continue
   456  				}
   457  
   458  				if method.CanInterface() {
   459  					contextType = method.Type().In(1)
   460  					// 添加注册方法
   461  					if useREST && (contextType == HttpContextType || contextType == ContextType) {
   462  						// 方法为对象方法名称 url 只注册对象名称
   463  						// name 为create, read, update, and delete (CRUD)等
   464  						name = ControllerMethodNameMapper(name) // 名称格式化
   465  						ul := &TUrl{Path: _path.Join(url.Path, name), Controller: objName, Action: name}
   466  						self.addRoute(position, hanadlerType, []string{"GET", "POST"}, ul, []any{method}, mids...)
   467  					}
   468  
   469  					if useRPC && (contextType == RpcContextType || contextType == ContextType) {
   470  						name = ControllerMethodNameMapper(name) // 名称格式化
   471  						ul := &TUrl{Path: strings.Join([]string{url.Path, name}, "."), Controller: objName, Action: name}
   472  						self.addRoute(position, hanadlerType, []string{"CONNECT"}, ul, []any{method}, mids...)
   473  					}
   474  				}
   475  			}
   476  
   477  			// the end of the struct mapping
   478  			return nil //TODO 返回路由
   479  		case reflect.Func:
   480  			// Method must be exported.
   481  			if ctrlType.PkgPath() != "" {
   482  				log.Fatalf("Method %s must be exported", url.Action)
   483  				return nil
   484  			}
   485  
   486  			// First arg must be context.Context
   487  			// RPC route validate
   488  			if utils.InStrings("CONNECT", methods...) > -1 {
   489  				ctxType := ctrlType.In(1)
   490  				if ctxType != RpcContextType && ctxType != ContextType {
   491  					log.Fatalf("method %s must use context pointer as the first parameter", url.Action)
   492  					return nil
   493  				}
   494  				hd = generateHandler(hanadlerType, RpcHandler, []interface{}{ctrlValue}, mids, url, nil)
   495  			} else {
   496  				hd = generateHandler(hanadlerType, HttpHandler, []interface{}{ctrlValue}, mids, url, nil)
   497  			}
   498  
   499  		default:
   500  			log.Fatal("controller must be func or bound method")
   501  		}
   502  	}
   503  
   504  	// trim the Url to including "/" on begin of path
   505  	if !strings.HasPrefix(url.Path, "/") && url.Path != "/" {
   506  		url.Path = "/" + url.Path
   507  	}
   508  
   509  	route := newRoute(self, methods, url, url.Path, self.config.FilePath, self.config.Name, url.Action)
   510  	route.handlers = append(route.handlers, hd)
   511  
   512  	// register route
   513  	self.tree.AddRoute(route)
   514  	return route
   515  }
   516  
   517  func (self *TGroup) IsService() bool {
   518  	return self.config.IsService
   519  }