@@ -23,6 +23,10 @@ type (
23
23
methodHandler * methodHandler
24
24
paramChild * node
25
25
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
26
30
}
27
31
kind uint8
28
32
children []* node
@@ -50,6 +54,20 @@ const (
50
54
anyLabel = byte ('*' )
51
55
)
52
56
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
+
53
71
// NewRouter returns a new Router instance.
54
72
func NewRouter (e * Echo ) * Router {
55
73
return & Router {
@@ -73,6 +91,11 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
73
91
pnames := []string {} // Param names
74
92
ppath := path // Pristine path
75
93
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
+
76
99
for i , lcpIndex := 0 , len (path ); i < lcpIndex ; i ++ {
77
100
if path [i ] == ':' {
78
101
j := i + 1
@@ -86,6 +109,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
86
109
i , lcpIndex = j , len (path )
87
110
88
111
if i == lcpIndex {
112
+ // path node is last fragment of route path. ie. `/users/:id`
89
113
r .insert (method , path [:i ], h , paramKind , ppath , pnames )
90
114
} else {
91
115
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
136
160
currentNode .ppath = ppath
137
161
currentNode .pnames = pnames
138
162
}
163
+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
139
164
} else if lcpLen < prefixLen {
140
165
// Split node
141
166
n := newNode (
@@ -149,7 +174,6 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
149
174
currentNode .paramChild ,
150
175
currentNode .anyChild ,
151
176
)
152
-
153
177
// Update parent path for all children to new node
154
178
for _ , child := range currentNode .staticChildren {
155
179
child .parent = n
@@ -171,6 +195,8 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
171
195
currentNode .pnames = nil
172
196
currentNode .paramChild = nil
173
197
currentNode .anyChild = nil
198
+ currentNode .isLeaf = false
199
+ currentNode .isHandler = false
174
200
175
201
// Only Static children could reach here
176
202
currentNode .addStaticChild (n )
@@ -188,6 +214,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
188
214
// Only Static children could reach here
189
215
currentNode .addStaticChild (n )
190
216
}
217
+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
191
218
} else if lcpLen < searchLen {
192
219
search = search [lcpLen :]
193
220
c := currentNode .findChildWithLabel (search [0 ])
@@ -207,6 +234,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
207
234
case anyKind :
208
235
currentNode .anyChild = n
209
236
}
237
+ currentNode .isLeaf = currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil
210
238
} else {
211
239
// Node already exists
212
240
if h != nil {
@@ -233,6 +261,8 @@ func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath
233
261
methodHandler : mh ,
234
262
paramChild : paramChildren ,
235
263
anyChild : anyChildren ,
264
+ isLeaf : sc == nil && paramChildren == nil && anyChildren == nil ,
265
+ isHandler : mh .isHandler (),
236
266
}
237
267
}
238
268
@@ -289,6 +319,12 @@ func (n *node) addHandler(method string, h HandlerFunc) {
289
319
case REPORT :
290
320
n .methodHandler .report = h
291
321
}
322
+
323
+ if h != nil {
324
+ n .isHandler = true
325
+ } else {
326
+ n .isHandler = n .methodHandler .isHandler ()
327
+ }
292
328
}
293
329
294
330
func (n * node ) findHandler (method string ) HandlerFunc {
@@ -343,6 +379,8 @@ func (r *Router) Find(method, path string, c Context) {
343
379
currentNode := r .tree // Current node as root
344
380
345
381
var (
382
+ previousBestMatchNode * node
383
+ matchedHandler HandlerFunc
346
384
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
347
385
// and search value gets shorter and shorter.
348
386
search = path
@@ -362,10 +400,11 @@ func (r *Router) Find(method, path string, c Context) {
362
400
valid = currentNode != nil
363
401
364
402
// 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
+ }
369
408
370
409
if fromKind == staticKind {
371
410
// 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) {
380
419
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
381
420
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
382
421
searchIndex -= len (paramValues [paramIndex ])
422
+ paramValues [paramIndex ] = ""
383
423
}
384
424
search = path [searchIndex :]
385
425
return
@@ -421,17 +461,25 @@ func (r *Router) Find(method, path string, c Context) {
421
461
// goto Any
422
462
} else {
423
463
// Not found (this should never be possible for static node we are looking currently)
424
- return
464
+ break
425
465
}
426
466
}
427
467
428
468
// The full prefix has matched, remove the prefix from the remaining search
429
469
search = search [lcpLen :]
430
470
searchIndex = searchIndex + lcpLen
431
471
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
+ }
435
483
}
436
484
437
485
// Static node
@@ -446,15 +494,16 @@ func (r *Router) Find(method, path string, c Context) {
446
494
// Param node
447
495
if child := currentNode .paramChild ; search != "" && child != nil {
448
496
currentNode = child
449
- // when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
450
- if currentNode .staticChildren == nil && currentNode .paramChild == nil && currentNode .anyChild == nil {
451
- paramValues [paramIndex ] = search
452
- break
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
+ }
453
505
}
454
506
455
- i , l := 0 , len (search )
456
- for ; i < l && search [i ] != '/' ; i ++ {
457
- }
458
507
paramValues [paramIndex ] = search [:i ]
459
508
paramIndex ++
460
509
search = search [i :]
@@ -468,29 +517,50 @@ func (r *Router) Find(method, path string, c Context) {
468
517
// If any node is found, use remaining path for paramValues
469
518
currentNode = child
470
519
paramValues [len (currentNode .pnames )- 1 ] = search
471
- 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
+ }
472
534
}
473
535
474
536
// Let's backtrack to the first possible alternative node of the decision path
475
537
nk , ok := backtrackToNextNodeKind (anyKind )
476
538
if ! ok {
477
- return // No other possibilities on the decision path
539
+ break // No other possibilities on the decision path
478
540
} else if nk == paramKind {
479
541
goto Param
480
542
} else if nk == anyKind {
481
543
goto Any
482
544
} else {
483
545
// Not found
484
- return
546
+ break
485
547
}
486
548
}
487
549
488
- ctx . handler = currentNode . findHandler ( method )
489
- ctx . path = currentNode . ppath
490
- ctx . pnames = currentNode . pnames
550
+ if currentNode == nil && previousBestMatchNode == nil {
551
+ return // nothing matched at all
552
+ }
491
553
492
- 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
493
560
ctx .handler = currentNode .checkMethodNotAllowed ()
494
561
}
562
+ ctx .path = currentNode .ppath
563
+ ctx .pnames = currentNode .pnames
564
+
495
565
return
496
566
}
0 commit comments