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 HandlerFunc
3134 }
3235 kind uint8
3336 children []* node
@@ -68,6 +71,7 @@ func (m *methodHandler) isHandler() bool {
6871 m .put != nil ||
6972 m .trace != nil ||
7073 m .report != nil
74+ // RouteNotFound/404 is not considered as a handler
7175}
7276
7377func (m * methodHandler ) updateAllowHeader () {
@@ -369,6 +373,9 @@ func (n *node) addHandler(method string, h HandlerFunc) {
369373 n .methodHandler .trace = h
370374 case REPORT :
371375 n .methodHandler .report = h
376+ case RouteNotFound :
377+ n .notFoundHandler = h
378+ return // RouteNotFound/404 is not considered as a handler so no further logic needs to be executed
372379 }
373380
374381 n .methodHandler .updateAllowHeader ()
@@ -403,7 +410,7 @@ func (n *node) findHandler(method string) HandlerFunc {
403410 return n .methodHandler .trace
404411 case REPORT :
405412 return n .methodHandler .report
406- default :
413+ default : // RouteNotFound/404 is not considered as a handler
407414 return nil
408415 }
409416}
@@ -506,7 +513,7 @@ func (r *Router) Find(method, path string, c Context) {
506513 // No matching prefix, let's backtrack to the first possible alternative node of the decision path
507514 nk , ok := backtrackToNextNodeKind (staticKind )
508515 if ! ok {
509- return // No other possibilities on the decision path
516+ return // No other possibilities on the decision path, handler will be whatever context is reset to.
510517 } else if nk == paramKind {
511518 goto Param
512519 // 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
@@ -522,15 +529,21 @@ func (r *Router) Find(method, path string, c Context) {
522529 search = search [lcpLen :]
523530 searchIndex = searchIndex + lcpLen
524531
525- // Finish routing if no remaining search and we are on a node with handler and matching method type
526- if search == "" && currentNode .isHandler {
527- // check if current node has handler registered for http method we are looking for. we store currentNode as
528- // best matching in case we do no find no more routes matching this path+method
529- if previousBestMatchNode == nil {
530- previousBestMatchNode = currentNode
531- }
532- if h := currentNode .findHandler (method ); h != nil {
533- matchedHandler = h
532+ // Finish routing if is no request path remaining to search
533+ if search == "" {
534+ // in case of node that is handler we have exact method type match or something for 405 to use
535+ if 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 .findHandler (method ); h != nil {
542+ matchedHandler = h
543+ break
544+ }
545+ } else if currentNode .notFoundHandler != nil {
546+ matchedHandler = currentNode .notFoundHandler
534547 break
535548 }
536549 }
@@ -550,7 +563,8 @@ func (r *Router) Find(method, path string, c Context) {
550563 i := 0
551564 l := len (search )
552565 if currentNode .isLeaf {
553- // when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
566+ // when param node does not have any children (path param is last piece of route path) then param node should
567+ // act similarly to any node - consider all remaining search as match
554568 i = l
555569 } else {
556570 for ; i < l && search [i ] != '/' ; i ++ {
@@ -575,13 +589,16 @@ func (r *Router) Find(method, path string, c Context) {
575589 searchIndex += + len (search )
576590 search = ""
577591
578- // check if current node has handler registered for http method we are looking for. we store currentNode as
579- // best matching in case we do no find no more routes matching this path+method
592+ if h := currentNode .findHandler (method ); h != nil {
593+ matchedHandler = h
594+ break
595+ }
596+ // we store currentNode as best matching in case we do not find more routes matching this path+method. Needed for 405
580597 if previousBestMatchNode == nil {
581598 previousBestMatchNode = currentNode
582599 }
583- if h := currentNode .findHandler ( method ); h != nil {
584- matchedHandler = h
600+ if currentNode .notFoundHandler != nil {
601+ matchedHandler = currentNode . notFoundHandler
585602 break
586603 }
587604 }
@@ -604,6 +621,8 @@ func (r *Router) Find(method, path string, c Context) {
604621 return // nothing matched at all
605622 }
606623
624+ // matchedHandler could be method+path handler that we matched or notFoundHandler from node with matching path
625+ // user provided not found (404) handler has priority over generic method not found (405) handler or global 404 handler
607626 if matchedHandler != nil {
608627 ctx .handler = matchedHandler
609628 } else {
@@ -612,7 +631,9 @@ func (r *Router) Find(method, path string, c Context) {
612631 currentNode = previousBestMatchNode
613632
614633 ctx .handler = NotFoundHandler
615- if currentNode .isHandler {
634+ if currentNode .notFoundHandler != nil {
635+ ctx .handler = currentNode .notFoundHandler
636+ } else if currentNode .isHandler {
616637 ctx .Set (ContextKeyHeaderAllow , currentNode .methodHandler .allowHeader )
617638 ctx .handler = MethodNotAllowedHandler
618639 if method == http .MethodOptions {
0 commit comments