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 }