@@ -23,6 +23,10 @@ type (
2323 methodHandler * methodHandler
2424 paramChild * node
2525 anyChild * node
26+ // isLeaf indicates that node does not have child routes
27+ isLeaf bool
28+ // isHandler indicates that node has at least one handler registered to it
29+ isHandler bool
2630 }
2731 kind uint8
2832 children []* node
@@ -50,6 +54,20 @@ const (
5054 anyLabel = byte ('*' )
5155)
5256
57+ func (m * methodHandler ) isHandler () bool {
58+ return m .connect != nil ||
59+ m .delete != nil ||
60+ m .get != nil ||
61+ m .head != nil ||
62+ m .options != nil ||
63+ m .patch != nil ||
64+ m .post != nil ||
65+ m .propfind != nil ||
66+ m .put != nil ||
67+ m .trace != nil ||
68+ m .report != nil
69+ }
70+
5371// NewRouter returns a new Router instance.
5472func NewRouter (e * Echo ) * Router {
5573 return & Router {
@@ -73,6 +91,11 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
7391 pnames := []string {} // Param names
7492 ppath := path // Pristine path
7593
94+ if h == nil && r .echo .Logger != nil {
95+ // FIXME: in future we should return error
96+ r .echo .Logger .Errorf ("Adding route without handler function: %v:%v" , method , path )
97+ }
98+
7699 for i , lcpIndex := 0 , len (path ); i < lcpIndex ; i ++ {
77100 if path [i ] == ':' {
78101 j := i + 1
@@ -86,6 +109,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
86109 i , lcpIndex = j , len (path )
87110
88111 if i == lcpIndex {
112+ // path node is last fragment of route path. ie. `/users/:id`
89113 r .insert (method , path [:i ], h , paramKind , ppath , pnames )
90114 } else {
91115 r .insert (method , path [:i ], nil , paramKind , "" , nil )
@@ -136,6 +160,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
136160 currentNode .ppath = ppath
137161 currentNode .pnames = pnames
138162 }
163+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
139164 } else if lcpLen < prefixLen {
140165 // Split node
141166 n := newNode (
@@ -149,7 +174,6 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
149174 currentNode .paramChild ,
150175 currentNode .anyChild ,
151176 )
152-
153177 // Update parent path for all children to new node
154178 for _ , child := range currentNode .staticChildren {
155179 child .parent = n
@@ -171,6 +195,8 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
171195 currentNode .pnames = nil
172196 currentNode .paramChild = nil
173197 currentNode .anyChild = nil
198+ currentNode .isLeaf = false
199+ currentNode .isHandler = false
174200
175201 // Only Static children could reach here
176202 currentNode .addStaticChild (n )
@@ -188,6 +214,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
188214 // Only Static children could reach here
189215 currentNode .addStaticChild (n )
190216 }
217+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
191218 } else if lcpLen < searchLen {
192219 search = search [lcpLen :]
193220 c := currentNode .findChildWithLabel (search [0 ])
@@ -207,6 +234,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
207234 case anyKind :
208235 currentNode .anyChild = n
209236 }
237+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
210238 } else {
211239 // Node already exists
212240 if h != nil {
@@ -233,6 +261,8 @@ func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath
233261 methodHandler : mh ,
234262 paramChild : paramChildren ,
235263 anyChild : anyChildren ,
264+ isLeaf : sc == nil && paramChildren == nil && anyChildren == nil ,
265+ isHandler : mh .isHandler (),
236266 }
237267}
238268
@@ -289,6 +319,12 @@ func (n *node) addHandler(method string, h HandlerFunc) {
289319 case REPORT :
290320 n .methodHandler .report = h
291321 }
322+
323+ if h != nil {
324+ n .isHandler = true
325+ } else {
326+ n .isHandler = n .methodHandler .isHandler ()
327+ }
292328}
293329
294330func (n * node ) findHandler (method string ) HandlerFunc {
@@ -343,6 +379,8 @@ func (r *Router) Find(method, path string, c Context) {
343379 currentNode := r .tree // Current node as root
344380
345381 var (
382+ previousBestMatchNode * node
383+ matchedHandler HandlerFunc
346384 // search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
347385 // and search value gets shorter and shorter.
348386 search = path
@@ -362,10 +400,11 @@ func (r *Router) Find(method, path string, c Context) {
362400 valid = currentNode != nil
363401
364402 // Next node type by priority
365- // NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
366- // always `static` or `any`
367- // If this is changed then for any route next kind would be `static` and this statement should be changed
368- nextNodeKind = previous .kind + 1
403+ if previous .kind == anyKind {
404+ nextNodeKind = staticKind
405+ } else {
406+ nextNodeKind = previous .kind + 1
407+ }
369408
370409 if fromKind == staticKind {
371410 // when backtracking is done from static kind block we did not change search so nothing to restore
@@ -380,6 +419,7 @@ func (r *Router) Find(method, path string, c Context) {
380419 // for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
381420 // for that index as it would also contain part of path we cut off before moving into node we are backtracking from
382421 searchIndex -= len (paramValues [paramIndex ])
422+ paramValues [paramIndex ] = ""
383423 }
384424 search = path [searchIndex :]
385425 return
@@ -421,17 +461,25 @@ func (r *Router) Find(method, path string, c Context) {
421461 // goto Any
422462 } else {
423463 // Not found (this should never be possible for static node we are looking currently)
424- return
464+ break
425465 }
426466 }
427467
428468 // The full prefix has matched, remove the prefix from the remaining search
429469 search = search [lcpLen :]
430470 searchIndex = searchIndex + lcpLen
431471
432- // Finish routing if no remaining search and we are on an leaf node
433- if search == "" && currentNode .ppath != "" {
434- break
472+ // Finish routing if no remaining search and we are on a node with handler and matching method type
473+ if search == "" && currentNode .isHandler {
474+ // check if current node has handler registered for http method we are looking for. we store currentNode as
475+ // best matching in case we do no find no more routes matching this path+method
476+ if previousBestMatchNode == nil {
477+ previousBestMatchNode = currentNode
478+ }
479+ if h := currentNode .findHandler (method ); h != nil {
480+ matchedHandler = h
481+ break
482+ }
435483 }
436484
437485 // Static node
@@ -446,10 +494,16 @@ func (r *Router) Find(method, path string, c Context) {
446494 // Param node
447495 if child := currentNode .paramChild ; search != "" && child != nil {
448496 currentNode = child
449- // FIXME: when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
450- i , l := 0 , len (search )
451- for ; i < l && search [i ] != '/' ; i ++ {
497+ i := 0
498+ l := len (search )
499+ if currentNode .isLeaf {
500+ // when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
501+ i = l
502+ } else {
503+ for ; i < l && search [i ] != '/' ; i ++ {
504+ }
452505 }
506+
453507 paramValues [paramIndex ] = search [:i ]
454508 paramIndex ++
455509 search = search [i :]
@@ -463,29 +517,50 @@ func (r *Router) Find(method, path string, c Context) {
463517 // If any node is found, use remaining path for paramValues
464518 currentNode = child
465519 paramValues [len (currentNode .pnames )- 1 ] = search
466- break
520+ // update indexes/search in case we need to backtrack when no handler match is found
521+ paramIndex ++
522+ searchIndex += + len (search )
523+ search = ""
524+
525+ // check if current node has handler registered for http method we are looking for. we store currentNode as
526+ // best matching in case we do no find no more routes matching this path+method
527+ if previousBestMatchNode == nil {
528+ previousBestMatchNode = currentNode
529+ }
530+ if h := currentNode .findHandler (method ); h != nil {
531+ matchedHandler = h
532+ break
533+ }
467534 }
468535
469536 // Let's backtrack to the first possible alternative node of the decision path
470537 nk , ok := backtrackToNextNodeKind (anyKind )
471538 if ! ok {
472- return // No other possibilities on the decision path
539+ break // No other possibilities on the decision path
473540 } else if nk == paramKind {
474541 goto Param
475542 } else if nk == anyKind {
476543 goto Any
477544 } else {
478545 // Not found
479- return
546+ break
480547 }
481548 }
482549
483- ctx . handler = currentNode . findHandler ( method )
484- ctx . path = currentNode . ppath
485- ctx . pnames = currentNode . pnames
550+ if currentNode == nil && previousBestMatchNode == nil {
551+ return // nothing matched at all
552+ }
486553
487- if ctx .handler == nil {
554+ if matchedHandler != nil {
555+ ctx .handler = matchedHandler
556+ } else {
557+ // use previous match as basis. although we have no matching handler we have path match.
558+ // so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
559+ currentNode = previousBestMatchNode
488560 ctx .handler = currentNode .checkMethodNotAllowed ()
489561 }
562+ ctx .path = currentNode .ppath
563+ ctx .pnames = currentNode .pnames
564+
490565 return
491566}
0 commit comments