github.com/zhongdalu/gf@v1.0.0/g/net/ghttp/ghttp_server_router_hook.go (about) 1 // Copyright 2018 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf. 6 // 事件回调(中间件)路由控制. 7 8 package ghttp 9 10 import ( 11 "container/list" 12 "fmt" 13 "github.com/zhongdalu/gf/g/container/gset" 14 "github.com/zhongdalu/gf/g/text/gregex" 15 "reflect" 16 "runtime" 17 "strings" 18 ) 19 20 // 绑定指定的hook回调函数, pattern参数同BindHandler,支持命名路由;hook参数的值由ghttp server设定,参数不区分大小写 21 func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) { 22 s.setHandler(pattern, &handlerItem{ 23 name: runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(), 24 ctype: nil, 25 fname: "", 26 faddr: handler, 27 }, hook) 28 } 29 30 // 通过map批量绑定回调函数 31 func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) { 32 for k, v := range hookmap { 33 s.BindHookHandler(pattern, k, v) 34 } 35 } 36 37 // 事件回调处理,内部使用了缓存处理. 38 // 并按照指定hook回调函数的优先级及注册顺序进行调用 39 func (s *Server) callHookHandler(hook string, r *Request) { 40 // 如果没有hook注册,那么不用执行后续逻辑 41 if len(s.hooksTree) == 0 { 42 return 43 } 44 hookItems := s.getHookHandlerWithCache(hook, r) 45 if len(hookItems) > 0 { 46 // 备份原有的router变量 47 oldRouterVars := r.routerVars 48 for _, item := range hookItems { 49 // hook方法不能更改serve方法的路由参数,其匹配的路由参数只能自己使用, 50 // 且在多个hook方法之间不能共享路由参数,单可以使用匹配的serve方法路由参数。 51 // 当前回调函数的路由参数只在当前回调函数下有效。 52 r.routerVars = make(map[string][]string) 53 if len(oldRouterVars) > 0 { 54 for k, v := range oldRouterVars { 55 r.routerVars[k] = v 56 } 57 } 58 if len(item.values) > 0 { 59 for k, v := range item.values { 60 r.routerVars[k] = v 61 } 62 } 63 // 不使用hook的router对象,保留路由注册服务的router对象,不能覆盖 64 // r.Router = item.handler.router 65 if err := s.niceCallHookHandler(item.handler.faddr, r); err != nil { 66 switch err { 67 case gEXCEPTION_EXIT: 68 break 69 case gEXCEPTION_EXIT_ALL: 70 fallthrough 71 case gEXCEPTION_EXIT_HOOK: 72 return 73 default: 74 panic(err) 75 } 76 } 77 } 78 // 恢复原有的router变量 79 r.routerVars = oldRouterVars 80 } 81 } 82 83 // 友好地调用方法 84 func (s *Server) niceCallHookHandler(f HandlerFunc, r *Request) (err interface{}) { 85 defer func() { 86 err = recover() 87 }() 88 f(r) 89 return 90 } 91 92 // 查询请求处理方法, 带缓存机制,按照Host、Method、Path进行缓存. 93 func (s *Server) getHookHandlerWithCache(hook string, r *Request) []*handlerParsedItem { 94 cacheItems := ([]*handlerParsedItem)(nil) 95 cacheKey := s.handlerKey(hook, r.Method, r.URL.Path, r.GetHost()) 96 if v := s.hooksCache.Get(cacheKey); v == nil { 97 cacheItems = s.searchHookHandler(r.Method, r.URL.Path, r.GetHost(), hook) 98 if cacheItems != nil { 99 s.hooksCache.Set(cacheKey, cacheItems, s.config.RouterCacheExpire*1000) 100 } 101 } else { 102 cacheItems = v.([]*handlerParsedItem) 103 } 104 return cacheItems 105 } 106 107 // 事件方法检索 108 func (s *Server) searchHookHandler(method, path, domain, hook string) []*handlerParsedItem { 109 if len(path) == 0 { 110 return nil 111 } 112 // 遍历检索的域名列表 113 domains := []string{gDEFAULT_DOMAIN} 114 if !strings.EqualFold(gDEFAULT_DOMAIN, domain) { 115 domains = append(domains, domain) 116 } 117 // URL.Path层级拆分 118 array := ([]string)(nil) 119 if strings.EqualFold("/", path) { 120 array = []string{"/"} 121 } else { 122 array = strings.Split(path[1:], "/") 123 } 124 parsedItems := make([]*handlerParsedItem, 0) 125 for _, domain := range domains { 126 p, ok := s.hooksTree[domain] 127 if !ok { 128 continue 129 } 130 p, ok = p.(map[string]interface{})[hook] 131 if !ok { 132 continue 133 } 134 // 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理 135 lists := make([]*list.List, 0) 136 for k, v := range array { 137 if _, ok := p.(map[string]interface{})["*list"]; ok { 138 lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) 139 } 140 if _, ok := p.(map[string]interface{})[v]; ok { 141 p = p.(map[string]interface{})[v] 142 if k == len(array)-1 { 143 if _, ok := p.(map[string]interface{})["*list"]; ok { 144 lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) 145 break 146 } 147 } 148 } else { 149 if _, ok := p.(map[string]interface{})["*fuzz"]; ok { 150 p = p.(map[string]interface{})["*fuzz"] 151 } 152 } 153 // 如果是叶子节点,同时判断当前层级的"*fuzz"键名,解决例如:/user/*action 匹配 /user 的规则 154 if k == len(array)-1 { 155 if _, ok := p.(map[string]interface{})["*fuzz"]; ok { 156 p = p.(map[string]interface{})["*fuzz"] 157 } 158 if _, ok := p.(map[string]interface{})["*list"]; ok { 159 lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) 160 } 161 } 162 } 163 164 // 多层链表遍历检索,从数组末尾的链表开始遍历,末尾的深度高优先级也高 165 pushedSet := gset.NewStringSet(true) 166 for i := len(lists) - 1; i >= 0; i-- { 167 for e := lists[i].Front(); e != nil; e = e.Next() { 168 handler := e.Value.(*handlerItem) 169 // 动态匹配规则带有gDEFAULT_METHOD的情况,不会像静态规则那样直接解析为所有的HTTP METHOD存储 170 if strings.EqualFold(handler.router.Method, gDEFAULT_METHOD) || strings.EqualFold(handler.router.Method, method) { 171 // 注意当不带任何动态路由规则时,len(match) == 1 172 if match, err := gregex.MatchString(handler.router.RegRule, path); err == nil && len(match) > 0 { 173 parsedItem := &handlerParsedItem{handler, nil} 174 // 如果需要query匹配,那么需要重新正则解析URL 175 if len(handler.router.RegNames) > 0 { 176 if len(match) > len(handler.router.RegNames) { 177 parsedItem.values = make(map[string][]string) 178 // 如果存在存在同名路由参数名称,那么执行数组追加 179 for i, name := range handler.router.RegNames { 180 if _, ok := parsedItem.values[name]; ok { 181 parsedItem.values[name] = append(parsedItem.values[name], match[i+1]) 182 } else { 183 parsedItem.values[name] = []string{match[i+1]} 184 } 185 } 186 } 187 } 188 address := fmt.Sprintf("%p", handler) 189 if !pushedSet.Contains(address) { 190 parsedItems = append(parsedItems, parsedItem) 191 pushedSet.Add(address) 192 } 193 } 194 } 195 } 196 } 197 return parsedItems 198 } 199 return nil 200 } 201 202 // 生成hook key,如果是hook key,那么使用'%'符号分隔 203 func (s *Server) handlerKey(hook, method, path, domain string) string { 204 return hook + "%" + s.serveHandlerKey(method, path, domain) 205 }