/
ghttp_server_router_hook.go
205 lines (195 loc) · 6.75 KB
/
ghttp_server_router_hook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 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)
}