126 |
137 |
|
func (n *node) addRoute(path string, handlers HandlersChain) { |
127 |
138 |
|
fullPath := path |
128 |
139 |
|
n.priority++ |
129 |
|
- |
numParams := countParams(path) |
|
140 |
+ |
|
|
141 |
+ |
// Empty tree |
|
142 |
+ |
if len(n.path) == 0 && len(n.children) == 0 { |
|
143 |
+ |
n.insertChild(path, fullPath, handlers) |
|
144 |
+ |
n.nType = root |
|
145 |
+ |
return |
|
146 |
+ |
} |
130 |
147 |
|
|
131 |
148 |
|
parentFullPathIndex := 0 |
132 |
149 |
|
|
133 |
|
- |
// non-empty tree |
134 |
|
- |
if len(n.path) > 0 || len(n.children) > 0 { |
135 |
|
- |
walk: |
136 |
|
- |
for { |
137 |
|
- |
// Update maxParams of the current node |
138 |
|
- |
if numParams > n.maxParams { |
139 |
|
- |
n.maxParams = numParams |
|
150 |
+ |
walk: |
|
151 |
+ |
for { |
|
152 |
+ |
// Find the longest common prefix. |
|
153 |
+ |
// This also implies that the common prefix contains no ':' or '*' |
|
154 |
+ |
// since the existing key can't contain those chars. |
|
155 |
+ |
i := longestCommonPrefix(path, n.path) |
|
156 |
+ |
|
|
157 |
+ |
// Split edge |
|
158 |
+ |
if i < len(n.path) { |
|
159 |
+ |
child := node{ |
|
160 |
+ |
path: n.path[i:], |
|
161 |
+ |
wildChild: n.wildChild, |
|
162 |
+ |
indices: n.indices, |
|
163 |
+ |
children: n.children, |
|
164 |
+ |
handlers: n.handlers, |
|
165 |
+ |
priority: n.priority - 1, |
|
166 |
+ |
fullPath: n.fullPath, |
140 |
167 |
|
} |
141 |
168 |
|
|
142 |
|
- |
// Find the longest common prefix. |
143 |
|
- |
// This also implies that the common prefix contains no ':' or '*' |
144 |
|
- |
// since the existing key can't contain those chars. |
145 |
|
- |
i := 0 |
146 |
|
- |
max := min(len(path), len(n.path)) |
147 |
|
- |
for i < max && path[i] == n.path[i] { |
148 |
|
- |
i++ |
149 |
|
- |
} |
|
169 |
+ |
n.children = []*node{&child} |
|
170 |
+ |
// []byte for proper unicode char conversion, see #65 |
|
171 |
+ |
n.indices = bytesconv.BytesToString([]byte{n.path[i]}) |
|
172 |
+ |
n.path = path[:i] |
|
173 |
+ |
n.handlers = nil |
|
174 |
+ |
n.wildChild = false |
|
175 |
+ |
n.fullPath = fullPath[:parentFullPathIndex+i] |
|
176 |
+ |
} |
150 |
177 |
|
|
151 |
|
- |
// Split edge |
152 |
|
- |
if i < len(n.path) { |
153 |
|
- |
child := node{ |
154 |
|
- |
path: n.path[i:], |
155 |
|
- |
wildChild: n.wildChild, |
156 |
|
- |
indices: n.indices, |
157 |
|
- |
children: n.children, |
158 |
|
- |
handlers: n.handlers, |
159 |
|
- |
priority: n.priority - 1, |
160 |
|
- |
fullPath: n.fullPath, |
161 |
|
- |
} |
|
178 |
+ |
// Make new node a child of this node |
|
179 |
+ |
if i < len(path) { |
|
180 |
+ |
path = path[i:] |
162 |
181 |
|
|
163 |
|
- |
// Update maxParams (max of all children) |
164 |
|
- |
for i := range child.children { |
165 |
|
- |
if child.children[i].maxParams > child.maxParams { |
166 |
|
- |
child.maxParams = child.children[i].maxParams |
167 |
|
- |
} |
|
182 |
+ |
if n.wildChild { |
|
183 |
+ |
parentFullPathIndex += len(n.path) |
|
184 |
+ |
n = n.children[0] |
|
185 |
+ |
n.priority++ |
|
186 |
+ |
|
|
187 |
+ |
// Check if the wildcard matches |
|
188 |
+ |
if len(path) >= len(n.path) && n.path == path[:len(n.path)] && |
|
189 |
+ |
// Adding a child to a catchAll is not possible |
|
190 |
+ |
n.nType != catchAll && |
|
191 |
+ |
// Check for longer wildcard, e.g. :name and :names |
|
192 |
+ |
(len(n.path) >= len(path) || path[len(n.path)] == '/') { |
|
193 |
+ |
continue walk |
168 |
194 |
|
} |
169 |
195 |
|
|
170 |
|
- |
n.children = []*node{&child} |
171 |
|
- |
// []byte for proper unicode char conversion, see #65 |
172 |
|
- |
n.indices = string([]byte{n.path[i]}) |
173 |
|
- |
n.path = path[:i] |
174 |
|
- |
n.handlers = nil |
175 |
|
- |
n.wildChild = false |
176 |
|
- |
n.fullPath = fullPath[:parentFullPathIndex+i] |
|
196 |
+ |
pathSeg := path |
|
197 |
+ |
if n.nType != catchAll { |
|
198 |
+ |
pathSeg = strings.SplitN(path, "/", 2)[0] |
|
199 |
+ |
} |
|
200 |
+ |
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path |
|
201 |
+ |
panic("'" + pathSeg + |
|
202 |
+ |
"' in new path '" + fullPath + |
|
203 |
+ |
"' conflicts with existing wildcard '" + n.path + |
|
204 |
+ |
"' in existing prefix '" + prefix + |
|
205 |
+ |
"'") |
177 |
206 |
|
} |
178 |
207 |
|
|
179 |
|
- |
// Make new node a child of this node |
180 |
|
- |
if i < len(path) { |
181 |
|
- |
path = path[i:] |
182 |
|
- |
|
183 |
|
- |
if n.wildChild { |
184 |
|
- |
parentFullPathIndex += len(n.path) |
185 |
|
- |
n = n.children[0] |
186 |
|
- |
n.priority++ |
187 |
|
- |
|
188 |
|
- |
// Update maxParams of the child node |
189 |
|
- |
if numParams > n.maxParams { |
190 |
|
- |
n.maxParams = numParams |
191 |
|
- |
} |
192 |
|
- |
numParams-- |
193 |
|
- |
|
194 |
|
- |
// Check if the wildcard matches |
195 |
|
- |
if len(path) >= len(n.path) && n.path == path[:len(n.path)] { |
196 |
|
- |
// check for longer wildcard, e.g. :name and :names |
197 |
|
- |
if len(n.path) >= len(path) || path[len(n.path)] == '/' { |
198 |
|
- |
continue walk |
199 |
|
- |
} |
200 |
|
- |
} |
|
208 |
+ |
c := path[0] |
201 |
209 |
|
|
202 |
|
- |
pathSeg := path |
203 |
|
- |
if n.nType != catchAll { |
204 |
|
- |
pathSeg = strings.SplitN(path, "/", 2)[0] |
205 |
|
- |
} |
206 |
|
- |
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path |
207 |
|
- |
panic("'" + pathSeg + |
208 |
|
- |
"' in new path '" + fullPath + |
209 |
|
- |
"' conflicts with existing wildcard '" + n.path + |
210 |
|
- |
"' in existing prefix '" + prefix + |
211 |
|
- |
"'") |
212 |
|
- |
} |
213 |
|
- |
|
214 |
|
- |
c := path[0] |
|
210 |
+ |
// slash after param |
|
211 |
+ |
if n.nType == param && c == '/' && len(n.children) == 1 { |
|
212 |
+ |
parentFullPathIndex += len(n.path) |
|
213 |
+ |
n = n.children[0] |
|
214 |
+ |
n.priority++ |
|
215 |
+ |
continue walk |
|
216 |
+ |
} |
215 |
217 |
|
|
216 |
|
- |
// slash after param |
217 |
|
- |
if n.nType == param && c == '/' && len(n.children) == 1 { |
|
218 |
+ |
// Check if a child with the next path byte exists |
|
219 |
+ |
for i, max := 0, len(n.indices); i < max; i++ { |
|
220 |
+ |
if c == n.indices[i] { |
218 |
221 |
|
parentFullPathIndex += len(n.path) |
219 |
|
- |
n = n.children[0] |
220 |
|
- |
n.priority++ |
|
222 |
+ |
i = n.incrementChildPrio(i) |
|
223 |
+ |
n = n.children[i] |
221 |
224 |
|
continue walk |
222 |
225 |
|
} |
|
226 |
+ |
} |
223 |
227 |
|
|
224 |
|
- |
// Check if a child with the next path byte exists |
225 |
|
- |
for i := 0; i < len(n.indices); i++ { |
226 |
|
- |
if c == n.indices[i] { |
227 |
|
- |
parentFullPathIndex += len(n.path) |
228 |
|
- |
i = n.incrementChildPrio(i) |
229 |
|
- |
n = n.children[i] |
230 |
|
- |
continue walk |
231 |
|
- |
} |
232 |
|
- |
} |
233 |
|
- |
|
234 |
|
- |
// Otherwise insert it |
235 |
|
- |
if c != ':' && c != '*' { |
236 |
|
- |
// []byte for proper unicode char conversion, see #65 |
237 |
|
- |
n.indices += string([]byte{c}) |
238 |
|
- |
child := &node{ |
239 |
|
- |
maxParams: numParams, |
240 |
|
- |
fullPath: fullPath, |
241 |
|
- |
} |
242 |
|
- |
n.children = append(n.children, child) |
243 |
|
- |
n.incrementChildPrio(len(n.indices) - 1) |
244 |
|
- |
n = child |
245 |
|
- |
} |
246 |
|
- |
n.insertChild(numParams, path, fullPath, handlers) |
247 |
|
- |
return |
248 |
|
- |
|
249 |
|
- |
} else if i == len(path) { // Make node a (in-path) leaf |
250 |
|
- |
if n.handlers != nil { |
251 |
|
- |
panic("handlers are already registered for path '" + fullPath + "'") |
|
228 |
+ |
// Otherwise insert it |
|
229 |
+ |
if c != ':' && c != '*' { |
|
230 |
+ |
// []byte for proper unicode char conversion, see #65 |
|
231 |
+ |
n.indices += bytesconv.BytesToString([]byte{c}) |
|
232 |
+ |
child := &node{ |
|
233 |
+ |
fullPath: fullPath, |
252 |
234 |
|
} |
253 |
|
- |
n.handlers = handlers |
|
235 |
+ |
n.children = append(n.children, child) |
|
236 |
+ |
n.incrementChildPrio(len(n.indices) - 1) |
|
237 |
+ |
n = child |
254 |
238 |
|
} |
|
239 |
+ |
n.insertChild(path, fullPath, handlers) |
255 |
240 |
|
return |
256 |
241 |
|
} |
257 |
|
- |
} else { // Empty tree |
258 |
|
- |
n.insertChild(numParams, path, fullPath, handlers) |
259 |
|
- |
n.nType = root |
|
242 |
+ |
|
|
243 |
+ |
// Otherwise and handle to current node |
|
244 |
+ |
if n.handlers != nil { |
|
245 |
+ |
panic("handlers are already registered for path '" + fullPath + "'") |
|
246 |
+ |
} |
|
247 |
+ |
n.handlers = handlers |
|
248 |
+ |
n.fullPath = fullPath |
|
249 |
+ |
return |
260 |
250 |
|
} |
261 |
251 |
|
} |
262 |
252 |
|
|
263 |
|
- |
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { |
264 |
|
- |
var offset int // already handled bytes of the path |
265 |
|
- |
|
266 |
|
- |
// find prefix until first wildcard (beginning with ':' or '*') |
267 |
|
- |
for i, max := 0, len(path); numParams > 0; i++ { |
268 |
|
- |
c := path[i] |
|
253 |
+ |
// Search for a wildcard segment and check the name for invalid characters. |
|
254 |
+ |
// Returns -1 as index, if no wildcard was found. |
|
255 |
+ |
func findWildcard(path string) (wildcard string, i int, valid bool) { |
|
256 |
+ |
// Find start |
|
257 |
+ |
for start, c := range []byte(path) { |
|
258 |
+ |
// A wildcard starts with ':' (param) or '*' (catch-all) |
269 |
259 |
|
if c != ':' && c != '*' { |
270 |
260 |
|
continue |
271 |
261 |
|
} |
272 |
262 |
|
|
273 |
|
- |
// find wildcard end (either '/' or path end) |
274 |
|
- |
end := i + 1 |
275 |
|
- |
for end < max && path[end] != '/' { |
276 |
|
- |
switch path[end] { |
277 |
|
- |
// the wildcard name must not contain ':' and '*' |
|
263 |
+ |
// Find end and check for invalid characters |
|
264 |
+ |
valid = true |
|
265 |
+ |
for end, c := range []byte(path[start+1:]) { |
|
266 |
+ |
switch c { |
|
267 |
+ |
case '/': |
|
268 |
+ |
return path[start : start+1+end], start, valid |
278 |
269 |
|
case ':', '*': |
279 |
|
- |
panic("only one wildcard per path segment is allowed, has: '" + |
280 |
|
- |
path[i:] + "' in path '" + fullPath + "'") |
281 |
|
- |
default: |
282 |
|
- |
end++ |
|
270 |
+ |
valid = false |
283 |
271 |
|
} |
284 |
272 |
|
} |
|
273 |
+ |
return path[start:], start, valid |
|
274 |
+ |
} |
|
275 |
+ |
return "", -1, false |
|
276 |
+ |
} |
285 |
277 |
|
|
286 |
|
- |
// check if this Node existing children which would be |
287 |
|
- |
// unreachable if we insert the wildcard here |
288 |
|
- |
if len(n.children) > 0 { |
289 |
|
- |
panic("wildcard route '" + path[i:end] + |
290 |
|
- |
"' conflicts with existing children in path '" + fullPath + "'") |
|
278 |
+ |
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) { |
|
279 |
+ |
for { |
|
280 |
+ |
// Find prefix until first wildcard |
|
281 |
+ |
wildcard, i, valid := findWildcard(path) |
|
282 |
+ |
if i < 0 { // No wildcard found |
|
283 |
+ |
break |
|
284 |
+ |
} |
|
285 |
+ |
|
|
286 |
+ |
// The wildcard name must not contain ':' and '*' |
|
287 |
+ |
if !valid { |
|
288 |
+ |
panic("only one wildcard per path segment is allowed, has: '" + |
|
289 |
+ |
wildcard + "' in path '" + fullPath + "'") |
291 |
290 |
|
} |
292 |
291 |
|
|
293 |
292 |
|
// check if the wildcard has a name |
294 |
|
- |
if end-i < 2 { |
|
293 |
+ |
if len(wildcard) < 2 { |
295 |
294 |
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") |
296 |
295 |
|
} |
297 |
296 |
|
|
298 |
|
- |
if c == ':' { // param |
299 |
|
- |
// split path at the beginning of the wildcard |
|
297 |
+ |
// Check if this node has existing children which would be |
|
298 |
+ |
// unreachable if we insert the wildcard here |
|
299 |
+ |
if len(n.children) > 0 { |
|
300 |
+ |
panic("wildcard segment '" + wildcard + |
|
301 |
+ |
"' conflicts with existing children in path '" + fullPath + "'") |
|
302 |
+ |
} |
|
303 |
+ |
|
|
304 |
+ |
if wildcard[0] == ':' { // param |
300 |
305 |
|
if i > 0 { |
301 |
|
- |
n.path = path[offset:i] |
302 |
|
- |
offset = i |
|
306 |
+ |
// Insert prefix before the current wildcard |
|
307 |
+ |
n.path = path[:i] |
|
308 |
+ |
path = path[i:] |
303 |
309 |
|
} |
304 |
310 |
|
|
|
311 |
+ |
n.wildChild = true |
305 |
312 |
|
child := &node{ |
306 |
|
- |
nType: param, |
307 |
|
- |
maxParams: numParams, |
308 |
|
- |
fullPath: fullPath, |
|
313 |
+ |
nType: param, |
|
314 |
+ |
path: wildcard, |
|
315 |
+ |
fullPath: fullPath, |
309 |
316 |
|
} |
310 |
317 |
|
n.children = []*node{child} |
311 |
|
- |
n.wildChild = true |
312 |
318 |
|
n = child |
313 |
319 |
|
n.priority++ |
314 |
|
- |
numParams-- |
315 |
320 |
|
|
316 |
321 |
|
// if the path doesn't end with the wildcard, then there |
317 |
322 |
|
// will be another non-wildcard subpath starting with '/' |
318 |
|
- |
if end < max { |
319 |
|
- |
n.path = path[offset:end] |
320 |
|
- |
offset = end |
|
323 |
+ |
if len(wildcard) < len(path) { |
|
324 |
+ |
path = path[len(wildcard):] |
321 |
325 |
|
|
322 |
326 |
|
child := &node{ |
323 |
|
- |
maxParams: numParams, |
324 |
|
- |
priority: 1, |
325 |
|
- |
fullPath: fullPath, |
|
327 |
+ |
priority: 1, |
|
328 |
+ |
fullPath: fullPath, |
326 |
329 |
|
} |
327 |
330 |
|
n.children = []*node{child} |
328 |
331 |
|
n = child |
|
332 |
+ |
continue |
329 |
333 |
|
} |
330 |
334 |
|
|
331 |
|
- |
} else { // catchAll |
332 |
|
- |
if end != max || numParams > 1 { |
333 |
|
- |
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") |
334 |
|
- |
} |
|
335 |
+ |
// Otherwise we're done. Insert the handle in the new leaf |
|
336 |
+ |
n.handlers = handlers |
|
337 |
+ |
return |
|
338 |
+ |
} |
335 |
339 |
|
|
336 |
|
- |
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { |
337 |
|
- |
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") |
338 |
|
- |
} |
|
340 |
+ |
// catchAll |
|
341 |
+ |
if i+len(wildcard) != len(path) { |
|
342 |
+ |
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") |
|
343 |
+ |
} |
339 |
344 |
|
|
340 |
|
- |
// currently fixed width 1 for '/' |
341 |
|
- |
i-- |
342 |
|
- |
if path[i] != '/' { |
343 |
|
- |
panic("no / before catch-all in path '" + fullPath + "'") |
344 |
|
- |
} |
|
345 |
+ |
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { |
|
346 |
+ |
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") |
|
347 |
+ |
} |
345 |
348 |
|
|
346 |
|
- |
n.path = path[offset:i] |
|
349 |
+ |
// currently fixed width 1 for '/' |
|
350 |
+ |
i-- |
|
351 |
+ |
if path[i] != '/' { |
|
352 |
+ |
panic("no / before catch-all in path '" + fullPath + "'") |
|
353 |
+ |
} |
347 |
354 |
|
|
348 |
|
- |
// first node: catchAll node with empty path |
349 |
|
- |
child := &node{ |
350 |
|
- |
wildChild: true, |
351 |
|
- |
nType: catchAll, |
352 |
|
- |
maxParams: 1, |
353 |
|
- |
fullPath: fullPath, |
354 |
|
- |
} |
355 |
|
- |
n.children = []*node{child} |
356 |
|
- |
n.indices = string(path[i]) |
357 |
|
- |
n = child |
358 |
|
- |
n.priority++ |
|
355 |
+ |
n.path = path[:i] |
359 |
356 |
|
|
360 |
|
- |
// second node: node holding the variable |
361 |
|
- |
child = &node{ |
362 |
|
- |
path: path[i:], |
363 |
|
- |
nType: catchAll, |
364 |
|
- |
maxParams: 1, |
365 |
|
- |
handlers: handlers, |
366 |
|
- |
priority: 1, |
367 |
|
- |
fullPath: fullPath, |
368 |
|
- |
} |
369 |
|
- |
n.children = []*node{child} |
|
357 |
+ |
// First node: catchAll node with empty path |
|
358 |
+ |
child := &node{ |
|
359 |
+ |
wildChild: true, |
|
360 |
+ |
nType: catchAll, |
|
361 |
+ |
fullPath: fullPath, |
|
362 |
+ |
} |
370 |
363 |
|
|
371 |
|
- |
return |
|
364 |
+ |
n.children = []*node{child} |
|
365 |
+ |
n.indices = string('/') |
|
366 |
+ |
n = child |
|
367 |
+ |
n.priority++ |
|
368 |
+ |
|
|
369 |
+ |
// second node: node holding the variable |
|
370 |
+ |
child = &node{ |
|
371 |
+ |
path: path[i:], |
|
372 |
+ |
nType: catchAll, |
|
373 |
+ |
handlers: handlers, |
|
374 |
+ |
priority: 1, |
|
375 |
+ |
fullPath: fullPath, |
372 |
376 |
|
} |
|
377 |
+ |
n.children = []*node{child} |
|
378 |
+ |
|
|
379 |
+ |
return |
373 |
380 |
|
} |
374 |
381 |
|
|
375 |
|
- |
// insert remaining path part and handle to the leaf |
376 |
|
- |
n.path = path[offset:] |
|
382 |
+ |
// If no wildcard was found, simply insert the path and handle |
|
383 |
+ |
n.path = path |
377 |
384 |
|
n.handlers = handlers |
378 |
385 |
|
n.fullPath = fullPath |
379 |
386 |
|
} |