@@ -2,7 +2,6 @@ package echo
22
33import (
44 "net/http"
5- "strings"
65)
76
87type (
@@ -334,21 +333,48 @@ func (r *Router) Find(method, path string, c Context) {
334333 cn := r .tree // Current node as root
335334
336335 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
344340 )
345341
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
350361 }
351362
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 {
352378 pl := 0 // Prefix length
353379 l := 0 // LCP length
354380
@@ -365,148 +391,82 @@ func (r *Router) Find(method, path string, c Context) {
365391 }
366392 }
367393
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
378407 }
379408 }
380409
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
393417 }
394418
395419 // 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
402424 }
403- cn = child
404- continue
405425 }
406426
407427 Param:
408428 // 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 {
422430 cn = child
423431 i , l := 0 , len (search )
424432 for ; i < l && search [i ] != '/' ; i ++ {
425433 }
426434 pvalues [n ] = search [:i ]
427435 n ++
428436 search = search [i :]
437+ searchIndex = searchIndex + i
429438 continue
430439 }
431440
432441 Any:
433442 // Any node
434- if cn = cn .anyChildren ; cn != nil {
443+ if child : = cn .anyChildren ; child != nil {
435444 // If any node is found, use remaining path for pvalues
445+ cn = child
436446 pvalues [len (cn .pnames )- 1 ] = search
437447 break
438448 }
439449
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
483461 }
484- return // Not found
485-
486462 }
487463
488464 ctx .handler = cn .findHandler (method )
489465 ctx .path = cn .ppath
490466 ctx .pnames = cn .pnames
491467
492- // NOTE: Slow zone...
493468 if ctx .handler == nil {
494469 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 ] = ""
509470 }
510-
511471 return
512472}
0 commit comments