// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. // 事件回调(中间件)路由控制. package ghttp import ( "container/list" "fmt" "github.com/gogf/gf/g/container/gset" "github.com/gogf/gf/g/text/gregex" "reflect" "runtime" "strings" ) // 绑定指定的hook回调函数, pattern参数同BindHandler,支持命名路由;hook参数的值由ghttp server设定,参数不区分大小写 func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) { s.setHandler(pattern, &handlerItem { name : runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(), ctype : nil, fname : "", faddr : handler, }, hook) } // 通过map批量绑定回调函数 func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) { for k, v := range hookmap { s.BindHookHandler(pattern, k, v) } } // 事件回调处理,内部使用了缓存处理. // 并按照指定hook回调函数的优先级及注册顺序进行调用 func (s *Server) callHookHandler(hook string, r *Request) { // 如果没有hook注册,那么不用执行后续逻辑 if len(s.hooksTree) == 0 { return } hookItems := s.getHookHandlerWithCache(hook, r) if len(hookItems) > 0 { // 备份原有的router变量 oldRouterVars := r.routerVars for _, item := range hookItems { // hook方法不能更改serve方法的路由参数,其匹配的路由参数只能自己使用, // 且在多个hook方法之间不能共享路由参数,单可以使用匹配的serve方法路由参数。 // 当前回调函数的路由参数只在当前回调函数下有效。 r.routerVars = make(map[string][]string) if len(oldRouterVars) > 0 { for k, v := range oldRouterVars { r.routerVars[k] = v } } if len(item.values) > 0 { for k, v := range item.values { r.routerVars[k] = v } } // 不使用hook的router对象,保留路由注册服务的router对象,不能覆盖 // r.Router = item.handler.router if err := s.niceCallHookHandler(item.handler.faddr, r); err != nil { switch err { case gEXCEPTION_EXIT: break case gEXCEPTION_EXIT_ALL: fallthrough case gEXCEPTION_EXIT_HOOK: return default: panic(err) } } } // 恢复原有的router变量 r.routerVars = oldRouterVars } } // 友好地调用方法 func (s *Server) niceCallHookHandler(f HandlerFunc, r *Request) (err interface{}) { defer func() { err = recover() }() f(r) return } // 查询请求处理方法, 带缓存机制,按照Host、Method、Path进行缓存. func (s *Server) getHookHandlerWithCache(hook string, r *Request) []*handlerParsedItem { cacheItems := ([]*handlerParsedItem)(nil) cacheKey := s.handlerKey(hook, r.Method, r.URL.Path, r.GetHost()) if v := s.hooksCache.Get(cacheKey); v == nil { cacheItems = s.searchHookHandler(r.Method, r.URL.Path, r.GetHost(), hook) if cacheItems != nil { s.hooksCache.Set(cacheKey, cacheItems, s.config.RouterCacheExpire*1000) } } else { cacheItems = v.([]*handlerParsedItem) } return cacheItems } // 事件方法检索 func (s *Server) searchHookHandler(method, path, domain, hook string) []*handlerParsedItem { if len(path) == 0 { return nil } // 遍历检索的域名列表 domains := []string{ gDEFAULT_DOMAIN } if !strings.EqualFold(gDEFAULT_DOMAIN, domain) { domains = append(domains, domain) } // URL.Path层级拆分 array := ([]string)(nil) if strings.EqualFold("/", path) { array = []string{"/"} } else { array = strings.Split(path[1:], "/") } parsedItems := make([]*handlerParsedItem, 0) for _, domain := range domains { p, ok := s.hooksTree[domain] if !ok { continue } p, ok = p.(map[string]interface{})[hook] if !ok { continue } // 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理 lists := make([]*list.List, 0) for k, v := range array { if _, ok := p.(map[string]interface{})["*list"]; ok { lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) } if _, ok := p.(map[string]interface{})[v]; ok { p = p.(map[string]interface{})[v] if k == len(array) - 1 { if _, ok := p.(map[string]interface{})["*list"]; ok { lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) break } } } else { if _, ok := p.(map[string]interface{})["*fuzz"]; ok { p = p.(map[string]interface{})["*fuzz"] } } // 如果是叶子节点,同时判断当前层级的"*fuzz"键名,解决例如:/user/*action 匹配 /user 的规则 if k == len(array) - 1 { if _, ok := p.(map[string]interface{})["*fuzz"]; ok { p = p.(map[string]interface{})["*fuzz"] } if _, ok := p.(map[string]interface{})["*list"]; ok { lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) } } } // 多层链表遍历检索,从数组末尾的链表开始遍历,末尾的深度高优先级也高 pushedSet := gset.NewStringSet(true) for i := len(lists) - 1; i >= 0; i-- { for e := lists[i].Front(); e != nil; e = e.Next() { handler := e.Value.(*handlerItem) // 动态匹配规则带有gDEFAULT_METHOD的情况,不会像静态规则那样直接解析为所有的HTTP METHOD存储 if strings.EqualFold(handler.router.Method, gDEFAULT_METHOD) || strings.EqualFold(handler.router.Method, method) { // 注意当不带任何动态路由规则时,len(match) == 1 if match, err := gregex.MatchString(handler.router.RegRule, path); err == nil && len(match) > 0 { parsedItem := &handlerParsedItem{handler, nil} // 如果需要query匹配,那么需要重新正则解析URL if len(handler.router.RegNames) > 0 { if len(match) > len(handler.router.RegNames) { parsedItem.values = make(map[string][]string) // 如果存在存在同名路由参数名称,那么执行数组追加 for i, name := range handler.router.RegNames { if _, ok := parsedItem.values[name]; ok { parsedItem.values[name] = append(parsedItem.values[name], match[i + 1]) } else { parsedItem.values[name] = []string{match[i + 1]} } } } } address := fmt.Sprintf("%p", handler) if !pushedSet.Contains(address) { parsedItems = append(parsedItems, parsedItem) pushedSet.Add(address) } } } } } return parsedItems } return nil } // 生成hook key,如果是hook key,那么使用'%'符号分隔 func (s *Server) handlerKey(hook, method, path, domain string) string { return hook + "%" + s.serveHandlerKey(method, path, domain) }