2828 isLeaf bool
2929 // isHandler indicates that node has at least one handler registered to it
3030 isHandler bool
31+
32+ // notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
33+ notFoundHandler * routeMethod
3134 }
3235 kind uint8
3336 children []* node
@@ -73,6 +76,7 @@ func (m *routeMethods) isHandler() bool {
7376 m .put != nil ||
7477 m .trace != nil ||
7578 m .report != nil
79+ // RouteNotFound/404 is not considered as a handler
7680}
7781
7882func (m * routeMethods ) updateAllowHeader () {
@@ -382,6 +386,9 @@ func (n *node) addMethod(method string, h *routeMethod) {
382386 n .methods .trace = h
383387 case REPORT :
384388 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
385392 }
386393
387394 n .methods .updateAllowHeader ()
@@ -412,7 +419,7 @@ func (n *node) findMethod(method string) *routeMethod {
412419 return n .methods .trace
413420 case REPORT :
414421 return n .methods .report
415- default :
422+ default : // RouteNotFound/404 is not considered as a handler
416423 return nil
417424 }
418425}
@@ -515,7 +522,7 @@ func (r *Router) Find(method, path string, c Context) {
515522 // No matching prefix, let's backtrack to the first possible alternative node of the decision path
516523 nk , ok := backtrackToNextNodeKind (staticKind )
517524 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.
519526 } else if nk == paramKind {
520527 goto Param
521528 // 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) {
531538 search = search [lcpLen :]
532539 searchIndex = searchIndex + lcpLen
533540
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
543556 break
544557 }
545558 }
@@ -559,7 +572,8 @@ func (r *Router) Find(method, path string, c Context) {
559572 i := 0
560573 l := len (search )
561574 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
563577 i = l
564578 } else {
565579 for ; i < l && search [i ] != '/' ; i ++ {
@@ -585,13 +599,16 @@ func (r *Router) Find(method, path string, c Context) {
585599 searchIndex += + len (search )
586600 search = ""
587601
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
590607 if previousBestMatchNode == nil {
591608 previousBestMatchNode = currentNode
592609 }
593- if h := currentNode .findMethod ( method ); h != nil {
594- matchedRouteMethod = h
610+ if currentNode .notFoundHandler != nil {
611+ matchedRouteMethod = currentNode . notFoundHandler
595612 break
596613 }
597614 }
@@ -614,12 +631,14 @@ func (r *Router) Find(method, path string, c Context) {
614631 return // nothing matched at all
615632 }
616633
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
617636 var rPath string
618637 var rPNames []string
619638 if matchedRouteMethod != nil {
620- ctx .handler = matchedRouteMethod .handler
621639 rPath = matchedRouteMethod .ppath
622640 rPNames = matchedRouteMethod .pnames
641+ ctx .handler = matchedRouteMethod .handler
623642 } else {
624643 // use previous match as basis. although we have no matching handler we have path match.
625644 // 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) {
628647 rPath = currentNode .originalPath
629648 rPNames = nil // no params here
630649 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 {
632655 ctx .Set (ContextKeyHeaderAllow , currentNode .methods .allowHeader )
633656 ctx .handler = MethodNotAllowedHandler
634657 if method == http .MethodOptions {
0 commit comments