28
28
isLeaf bool
29
29
// isHandler indicates that node has at least one handler registered to it
30
30
isHandler bool
31
+
32
+ // notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
33
+ notFoundHandler * routeMethod
31
34
}
32
35
kind uint8
33
36
children []* node
@@ -73,6 +76,7 @@ func (m *routeMethods) isHandler() bool {
73
76
m .put != nil ||
74
77
m .trace != nil ||
75
78
m .report != nil
79
+ // RouteNotFound/404 is not considered as a handler
76
80
}
77
81
78
82
func (m * routeMethods ) updateAllowHeader () {
@@ -382,6 +386,9 @@ func (n *node) addMethod(method string, h *routeMethod) {
382
386
n .methods .trace = h
383
387
case REPORT :
384
388
n .methods .report = h
389
+ case RouteNotFound :
390
+ n .notFoundHandler = h
391
+ return // RouteNotFound/404 is not considered as a handler so no further logic needs to be executed
385
392
}
386
393
387
394
n .methods .updateAllowHeader ()
@@ -412,7 +419,7 @@ func (n *node) findMethod(method string) *routeMethod {
412
419
return n .methods .trace
413
420
case REPORT :
414
421
return n .methods .report
415
- default :
422
+ default : // RouteNotFound/404 is not considered as a handler
416
423
return nil
417
424
}
418
425
}
@@ -515,7 +522,7 @@ func (r *Router) Find(method, path string, c Context) {
515
522
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
516
523
nk , ok := backtrackToNextNodeKind (staticKind )
517
524
if ! ok {
518
- return // No other possibilities on the decision path
525
+ return // No other possibilities on the decision path, handler will be whatever context is reset to.
519
526
} else if nk == paramKind {
520
527
goto Param
521
528
// 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
@@ -531,15 +538,21 @@ func (r *Router) Find(method, path string, c Context) {
531
538
search = search [lcpLen :]
532
539
searchIndex = searchIndex + lcpLen
533
540
534
- // Finish routing if no remaining search and we are on a node with handler and matching method type
535
- if search == "" && currentNode .isHandler {
536
- // check if current node has handler registered for http method we are looking for. we store currentNode as
537
- // best matching in case we do no find no more routes matching this path+method
538
- if previousBestMatchNode == nil {
539
- previousBestMatchNode = currentNode
540
- }
541
- if h := currentNode .findMethod (method ); h != nil {
542
- matchedRouteMethod = h
541
+ // Finish routing if is no request path remaining to search
542
+ if search == "" {
543
+ // in case of node that is handler we have exact method type match or something for 405 to use
544
+ if currentNode .isHandler {
545
+ // check if current node has handler registered for http method we are looking for. we store currentNode as
546
+ // best matching in case we do no find no more routes matching this path+method
547
+ if previousBestMatchNode == nil {
548
+ previousBestMatchNode = currentNode
549
+ }
550
+ if h := currentNode .findMethod (method ); h != nil {
551
+ matchedRouteMethod = h
552
+ break
553
+ }
554
+ } else if currentNode .notFoundHandler != nil {
555
+ matchedRouteMethod = currentNode .notFoundHandler
543
556
break
544
557
}
545
558
}
@@ -559,7 +572,8 @@ func (r *Router) Find(method, path string, c Context) {
559
572
i := 0
560
573
l := len (search )
561
574
if currentNode .isLeaf {
562
- // when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
575
+ // when param node does not have any children (path param is last piece of route path) then param node should
576
+ // act similarly to any node - consider all remaining search as match
563
577
i = l
564
578
} else {
565
579
for ; i < l && search [i ] != '/' ; i ++ {
@@ -585,13 +599,16 @@ func (r *Router) Find(method, path string, c Context) {
585
599
searchIndex += + len (search )
586
600
search = ""
587
601
588
- // check if current node has handler registered for http method we are looking for. we store currentNode as
589
- // best matching in case we do no find no more routes matching this path+method
602
+ if h := currentNode .findMethod (method ); h != nil {
603
+ matchedRouteMethod = h
604
+ break
605
+ }
606
+ // we store currentNode as best matching in case we do not find more routes matching this path+method. Needed for 405
590
607
if previousBestMatchNode == nil {
591
608
previousBestMatchNode = currentNode
592
609
}
593
- if h := currentNode .findMethod ( method ); h != nil {
594
- matchedRouteMethod = h
610
+ if currentNode .notFoundHandler != nil {
611
+ matchedRouteMethod = currentNode . notFoundHandler
595
612
break
596
613
}
597
614
}
@@ -614,12 +631,14 @@ func (r *Router) Find(method, path string, c Context) {
614
631
return // nothing matched at all
615
632
}
616
633
634
+ // matchedHandler could be method+path handler that we matched or notFoundHandler from node with matching path
635
+ // user provided not found (404) handler has priority over generic method not found (405) handler or global 404 handler
617
636
var rPath string
618
637
var rPNames []string
619
638
if matchedRouteMethod != nil {
620
- ctx .handler = matchedRouteMethod .handler
621
639
rPath = matchedRouteMethod .ppath
622
640
rPNames = matchedRouteMethod .pnames
641
+ ctx .handler = matchedRouteMethod .handler
623
642
} else {
624
643
// use previous match as basis. although we have no matching handler we have path match.
625
644
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
@@ -628,7 +647,11 @@ func (r *Router) Find(method, path string, c Context) {
628
647
rPath = currentNode .originalPath
629
648
rPNames = nil // no params here
630
649
ctx .handler = NotFoundHandler
631
- if currentNode .isHandler {
650
+ if currentNode .notFoundHandler != nil {
651
+ rPath = currentNode .notFoundHandler .ppath
652
+ rPNames = currentNode .notFoundHandler .pnames
653
+ ctx .handler = currentNode .notFoundHandler .handler
654
+ } else if currentNode .isHandler {
632
655
ctx .Set (ContextKeyHeaderAllow , currentNode .methods .allowHeader )
633
656
ctx .handler = MethodNotAllowedHandler
634
657
if method == http .MethodOptions {
0 commit comments