-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrax.lua
More file actions
252 lines (221 loc) · 5.98 KB
/
Copy pathrax.lua
File metadata and controls
252 lines (221 loc) · 5.98 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
local rax_core = require "rax.core"
local function gc_free(self)
rax_core.destroy(self.tree)
rax_core.stop(self.tree_it)
end
local M = { VERSION = '0.0.1' }
local mt = { __index = M, __gc = gc_free }
local log_info = function () end
local log_debug = function () end
--local log_info = print
--local log_debug = print
local _METHOD_GET = 2
local _METHOD_POST = 2 << 1
local _METHOD_PUT = 2 << 2
local _METHOD_DELETE = 2 << 3
local _METHOD_PATCH = 2 << 4
local _METHOD_HEAD = 2 << 5
local _METHOD_OPTIONS = 2 << 6
local _METHODS = {
GET = _METHOD_GET,
POST = _METHOD_POST,
PUT = _METHOD_PUT,
DELETE = _METHOD_DELETE,
PATCH = _METHOD_PATCH,
HEAD = _METHOD_HEAD,
OPTIONS = _METHOD_OPTIONS,
}
function M:new()
local tree = rax_core.new()
local tree_it = rax_core.newit(tree)
local instance = {
tree = tree,
tree_it = tree_it,
match_data_index = 0,
match_data = {},
hash_path = {},
hash_pattern = {},
}
return setmetatable(instance, mt)
end
function M:insert(method, path, data)
if type(path) ~= "string" then
error("invalid argument path")
end
if (not method) or (not path) or (not data) then
error("invalid argument of route")
end
local bit_methods
if type(method) ~= "table" then
bit_methods = method and _METHODS[method] or 0
else
bit_methods = 0
for _, m in ipairs(method) do
bit_methods = bit_methods | _METHODS[m]
end
end
local opts = {
data = data,
method = bit_methods,
path_org = path,
param = false,
}
local pos = path:find(":", 1, true)
if pos then
path = path:sub(1, pos -1)
opts.path_op = "<="
opts.path = path
opts.param = true
else
pos = path:find("*", 1, true)
if pos then
if pos ~= #path then
opts.param = true
end
path = path:sub(1, pos - 1)
opts.path = "<="
else
opts.path_op = "="
end
opts.path = path
end
if opts.path_op == "=" then
if not self.hash_path[path] then
self.hash_path[path] = {opts}
else
table.insert(self.hash_path[path], opts)
end
return true
end
local idx = rax_core.find(self.tree, path)
if idx ~= nil then
local routes = self.match_data[idx]
if routes and routes[1].path == path then
table.insert(routes, opts)
return true
end
end
self.match_data_index = self.match_data_index + 1
self.match_data[self.match_data_index] = {opts}
log_info("insert route path: ", path, " dataprt: ", self.match_data_index)
return rax_core.insert(self.tree, path, self.match_data_index)
end
-- compat for lua-r3
function M:compile()
return true
end
local function _match_route_opts(route, opts)
local method = opts.method
if route.method ~= 0 then
if (not method)
or (type(_METHODS[method]) ~= "number")
or (route.method & _METHODS[method] == 0) then
return false
end
end
opts.matched._method = method
return true
end
function M:_fetch_pat(path)
local pat = self.hash_pattern[path]
if pat then
return pat[1], pat[2] -- pat, names
end
local i = 0
local j = 0
local nameidx = 0
local names = {}
local res = {}
for item in path:gmatch("[^/]+") do
j = j + 1
res[j] = item
local first_byte = item:byte(1, 1)
if first_byte == string.byte(":") then
i = i + 1
names[i] = item:sub(2)
-- See https://www.rfc-editor.org/rfc/rfc1738.txt BNF for specific URL schemes
res[j] = [=[([%w%-_;:@&=!',%%%$%.%+%*%(%)]+)]=]
elseif first_byte == string.byte("*") then
local name = item:sub(2)
if name == "" then
nameidx = nameidx + 1
name = nameidx
end
i = i + 1
names[i] = name
-- '.' matches any character except newline
res[j] = [=[(.*)]=]
end
end
pat = table.concat(res, [[/]])
self.hash_pattern[path] = {pat, names}
return pat, names
end
function M:_compare_param(req_path, route, opts)
if not route.param then
return true
end
local pat, names = self:_fetch_pat(route.path_org)
log_debug("pat: ", pat)
if #names == 0 then
return true
end
local m = table.pack(string.gmatch(req_path, pat)())
if not m[1] then
return false
end
for i,v in ipairs(m) do
local name = names[i]
if name and v then
opts.matched[name] = v
end
end
return true
end
function M:_match_from_routes(routes, path, opts)
for _, route in ipairs(routes) do
if _match_route_opts(route, opts) then
if self:_compare_param(path, route, opts) then
opts.matched._path = route.path_org
return route
end
end
end
return nil
end
function M:match(path, method)
local opts = {
method = method,
matched = {},
}
local routes = self.hash_path[path]
if routes then
for _, route in ipairs(routes) do
if _match_route_opts(route, opts) then
opts.matched._path = path
return route.data, opts.matched
end
end
end
local ret = rax_core.search(self.tree_it, path)
if not ret then
return
end
while true do
local idx = rax_core.prev(self.tree_it, path)
if idx <= 0 then
break
end
routes = self.match_data[idx]
if routes then
local route = self:_match_from_routes(routes, path, opts)
if route then
return route.data, opts.matched
end
end
end
end
function M:dump()
rax_core.dump(self.tree)
end
return M