@@ -2,7 +2,6 @@ package echo
2
2
3
3
import (
4
4
"net/http"
5
- "strings"
6
5
)
7
6
8
7
type (
@@ -334,21 +333,48 @@ func (r *Router) Find(method, path string, c Context) {
334
333
cn := r .tree // Current node as root
335
334
336
335
var (
337
- search = path
338
- child * node // Child node
339
- n int // Param counter
340
- nk kind // Next kind
341
- nn * node // Next node
342
- ns string // Next search
343
- pvalues = ctx .pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
336
+ search = path
337
+ searchIndex = 0
338
+ n int // Param counter
339
+ pvalues = ctx .pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
344
340
)
345
341
346
- // Search order static > param > any
347
- for {
348
- if search == "" {
349
- break
342
+ // Backtracking is needed when a dead end (leaf node) is reached in the router tree.
343
+ // To backtrack the current node will be changed to the parent node and the next kind for the
344
+ // router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
345
+ // For example if there is no static node match we should check parent next sibling by kind (param).
346
+ // Backtracking itself does not check if there is a next sibling, this is done by the router logic.
347
+ backtrackToNextNodeKind := func (fromKind kind ) (nextNodeKind kind , valid bool ) {
348
+ previous := cn
349
+ cn = previous .parent
350
+ valid = cn != nil
351
+
352
+ // Next node type by priority
353
+ // NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
354
+ // always `static` or `any`
355
+ // If this is changed then for any route next kind would be `static` and this statement should be changed
356
+ nextNodeKind = previous .kind + 1
357
+
358
+ if fromKind == skind {
359
+ // when backtracking is done from static kind block we did not change search so nothing to restore
360
+ return
350
361
}
351
362
363
+ // restore search to value it was before we move to current node we are backtracking from.
364
+ if previous .kind == skind {
365
+ searchIndex -= len (previous .prefix )
366
+ } else {
367
+ n --
368
+ // for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
369
+ // for that index as it would also contain part of path we cut off before moving into node we are backtracking from
370
+ searchIndex -= len (pvalues [n ])
371
+ }
372
+ search = path [searchIndex :]
373
+ return
374
+ }
375
+
376
+ // Search order static > param > any
377
+ for {
352
378
pl := 0 // Prefix length
353
379
l := 0 // LCP length
354
380
@@ -365,148 +391,82 @@ func (r *Router) Find(method, path string, c Context) {
365
391
}
366
392
}
367
393
368
- if l == pl {
369
- // Continue search
370
- search = search [l :]
371
- // Finish routing if no remaining search and we are on an leaf node
372
- if search == "" && (nn == nil || cn .parent == nil || cn .ppath != "" ) {
373
- break
374
- }
375
- // Handle special case of trailing slash route with existing any route (see #1526)
376
- if search == "" && path [len (path )- 1 ] == '/' && cn .anyChildren != nil {
377
- goto Any
394
+ if l != pl {
395
+ // No matching prefix, let's backtrack to the first possible alternative node of the decision path
396
+ nk , ok := backtrackToNextNodeKind (skind )
397
+ if ! ok {
398
+ return // No other possibilities on the decision path
399
+ } else if nk == pkind {
400
+ goto Param
401
+ // NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
402
+ //} else if nk == akind {
403
+ // goto Any
404
+ } else {
405
+ // Not found (this should never be possible for static node we are looking currently)
406
+ return
378
407
}
379
408
}
380
409
381
- // Attempt to go back up the tree on no matching prefix or no remaining search
382
- if l != pl || search == "" {
383
- if nn == nil { // Issue #1348
384
- return // Not found
385
- }
386
- cn = nn
387
- search = ns
388
- if nk == pkind {
389
- goto Param
390
- } else if nk == akind {
391
- goto Any
392
- }
410
+ // The full prefix has matched, remove the prefix from the remaining search
411
+ search = search [l :]
412
+ searchIndex = searchIndex + l
413
+
414
+ // Finish routing if no remaining search and we are on an leaf node
415
+ if search == "" && cn .ppath != "" {
416
+ break
393
417
}
394
418
395
419
// Static node
396
- if child = cn .findStaticChild (search [0 ]); child != nil {
397
- // Save next
398
- if cn .prefix [len (cn .prefix )- 1 ] == '/' { // Issue #623
399
- nk = pkind
400
- nn = cn
401
- ns = search
420
+ if search != "" {
421
+ if child := cn .findStaticChild (search [0 ]); child != nil {
422
+ cn = child
423
+ continue
402
424
}
403
- cn = child
404
- continue
405
425
}
406
426
407
427
Param:
408
428
// Param node
409
- if child = cn .paramChildren ; child != nil {
410
- // Issue #378
411
- if len (pvalues ) == n {
412
- continue
413
- }
414
-
415
- // Save next
416
- if cn .prefix [len (cn .prefix )- 1 ] == '/' { // Issue #623
417
- nk = akind
418
- nn = cn
419
- ns = search
420
- }
421
-
429
+ if child := cn .paramChildren ; search != "" && child != nil {
422
430
cn = child
423
431
i , l := 0 , len (search )
424
432
for ; i < l && search [i ] != '/' ; i ++ {
425
433
}
426
434
pvalues [n ] = search [:i ]
427
435
n ++
428
436
search = search [i :]
437
+ searchIndex = searchIndex + i
429
438
continue
430
439
}
431
440
432
441
Any:
433
442
// Any node
434
- if cn = cn .anyChildren ; cn != nil {
443
+ if child : = cn .anyChildren ; child != nil {
435
444
// If any node is found, use remaining path for pvalues
445
+ cn = child
436
446
pvalues [len (cn .pnames )- 1 ] = search
437
447
break
438
448
}
439
449
440
- // No node found, continue at stored next node
441
- // or find nearest "any" route
442
- if nn != nil {
443
- // No next node to go down in routing (issue #954)
444
- // Find nearest "any" route going up the routing tree
445
- search = ns
446
- np := nn .parent
447
- // Consider param route one level up only
448
- if cn = nn .paramChildren ; cn != nil {
449
- pos := strings .IndexByte (ns , '/' )
450
- if pos == - 1 {
451
- // If no slash is remaining in search string set param value
452
- if len (cn .pnames ) > 0 {
453
- pvalues [len (cn .pnames )- 1 ] = search
454
- }
455
- break
456
- } else if pos > 0 {
457
- // Otherwise continue route processing with restored next node
458
- cn = nn
459
- nn = nil
460
- ns = ""
461
- goto Param
462
- }
463
- }
464
- // No param route found, try to resolve nearest any route
465
- for {
466
- np = nn .parent
467
- if cn = nn .anyChildren ; cn != nil {
468
- break
469
- }
470
- if np == nil {
471
- break // no further parent nodes in tree, abort
472
- }
473
- var str strings.Builder
474
- str .WriteString (nn .prefix )
475
- str .WriteString (search )
476
- search = str .String ()
477
- nn = np
478
- }
479
- if cn != nil { // use the found "any" route and update path
480
- pvalues [len (cn .pnames )- 1 ] = search
481
- break
482
- }
450
+ // Let's backtrack to the first possible alternative node of the decision path
451
+ nk , ok := backtrackToNextNodeKind (akind )
452
+ if ! ok {
453
+ return // No other possibilities on the decision path
454
+ } else if nk == pkind {
455
+ goto Param
456
+ } else if nk == akind {
457
+ goto Any
458
+ } else {
459
+ // Not found
460
+ return
483
461
}
484
- return // Not found
485
-
486
462
}
487
463
488
464
ctx .handler = cn .findHandler (method )
489
465
ctx .path = cn .ppath
490
466
ctx .pnames = cn .pnames
491
467
492
- // NOTE: Slow zone...
493
468
if ctx .handler == nil {
494
469
ctx .handler = cn .checkMethodNotAllowed ()
495
-
496
- // Dig further for any, might have an empty value for *, e.g.
497
- // serving a directory. Issue #207.
498
- if cn = cn .anyChildren ; cn == nil {
499
- return
500
- }
501
- if h := cn .findHandler (method ); h != nil {
502
- ctx .handler = h
503
- } else {
504
- ctx .handler = cn .checkMethodNotAllowed ()
505
- }
506
- ctx .path = cn .ppath
507
- ctx .pnames = cn .pnames
508
- pvalues [len (cn .pnames )- 1 ] = ""
509
470
}
510
-
511
471
return
512
472
}
0 commit comments