tree.go (22363B)
1 // Copyright 2013 Julien Schmidt. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be found 3 // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE 4 5 package gin 6 7 import ( 8 "bytes" 9 "net/url" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 14 "github.com/gin-gonic/gin/internal/bytesconv" 15 ) 16 17 var ( 18 strColon = []byte(":") 19 strStar = []byte("*") 20 strSlash = []byte("/") 21 ) 22 23 // Param is a single URL parameter, consisting of a key and a value. 24 type Param struct { 25 Key string 26 Value string 27 } 28 29 // Params is a Param-slice, as returned by the router. 30 // The slice is ordered, the first URL parameter is also the first slice value. 31 // It is therefore safe to read values by the index. 32 type Params []Param 33 34 // Get returns the value of the first Param which key matches the given name and a boolean true. 35 // If no matching Param is found, an empty string is returned and a boolean false . 36 func (ps Params) Get(name string) (string, bool) { 37 for _, entry := range ps { 38 if entry.Key == name { 39 return entry.Value, true 40 } 41 } 42 return "", false 43 } 44 45 // ByName returns the value of the first Param which key matches the given name. 46 // If no matching Param is found, an empty string is returned. 47 func (ps Params) ByName(name string) (va string) { 48 va, _ = ps.Get(name) 49 return 50 } 51 52 type methodTree struct { 53 method string 54 root *node 55 } 56 57 type methodTrees []methodTree 58 59 func (trees methodTrees) get(method string) *node { 60 for _, tree := range trees { 61 if tree.method == method { 62 return tree.root 63 } 64 } 65 return nil 66 } 67 68 func min(a, b int) int { 69 if a <= b { 70 return a 71 } 72 return b 73 } 74 75 func longestCommonPrefix(a, b string) int { 76 i := 0 77 max := min(len(a), len(b)) 78 for i < max && a[i] == b[i] { 79 i++ 80 } 81 return i 82 } 83 84 // addChild will add a child node, keeping wildcardChild at the end 85 func (n *node) addChild(child *node) { 86 if n.wildChild && len(n.children) > 0 { 87 wildcardChild := n.children[len(n.children)-1] 88 n.children = append(n.children[:len(n.children)-1], child, wildcardChild) 89 } else { 90 n.children = append(n.children, child) 91 } 92 } 93 94 func countParams(path string) uint16 { 95 var n uint16 96 s := bytesconv.StringToBytes(path) 97 n += uint16(bytes.Count(s, strColon)) 98 n += uint16(bytes.Count(s, strStar)) 99 return n 100 } 101 102 func countSections(path string) uint16 { 103 s := bytesconv.StringToBytes(path) 104 return uint16(bytes.Count(s, strSlash)) 105 } 106 107 type nodeType uint8 108 109 const ( 110 static nodeType = iota 111 root 112 param 113 catchAll 114 ) 115 116 type node struct { 117 path string 118 indices string 119 wildChild bool 120 nType nodeType 121 priority uint32 122 children []*node // child nodes, at most 1 :param style node at the end of the array 123 handlers HandlersChain 124 fullPath string 125 } 126 127 // Increments priority of the given child and reorders if necessary 128 func (n *node) incrementChildPrio(pos int) int { 129 cs := n.children 130 cs[pos].priority++ 131 prio := cs[pos].priority 132 133 // Adjust position (move to front) 134 newPos := pos 135 for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- { 136 // Swap node positions 137 cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1] 138 } 139 140 // Build new index char string 141 if newPos != pos { 142 n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty 143 n.indices[pos:pos+1] + // The index char we move 144 n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos' 145 } 146 147 return newPos 148 } 149 150 // addRoute adds a node with the given handle to the path. 151 // Not concurrency-safe! 152 func (n *node) addRoute(path string, handlers HandlersChain) { 153 fullPath := path 154 n.priority++ 155 156 // Empty tree 157 if len(n.path) == 0 && len(n.children) == 0 { 158 n.insertChild(path, fullPath, handlers) 159 n.nType = root 160 return 161 } 162 163 parentFullPathIndex := 0 164 165 walk: 166 for { 167 // Find the longest common prefix. 168 // This also implies that the common prefix contains no ':' or '*' 169 // since the existing key can't contain those chars. 170 i := longestCommonPrefix(path, n.path) 171 172 // Split edge 173 if i < len(n.path) { 174 child := node{ 175 path: n.path[i:], 176 wildChild: n.wildChild, 177 nType: static, 178 indices: n.indices, 179 children: n.children, 180 handlers: n.handlers, 181 priority: n.priority - 1, 182 fullPath: n.fullPath, 183 } 184 185 n.children = []*node{&child} 186 // []byte for proper unicode char conversion, see #65 187 n.indices = bytesconv.BytesToString([]byte{n.path[i]}) 188 n.path = path[:i] 189 n.handlers = nil 190 n.wildChild = false 191 n.fullPath = fullPath[:parentFullPathIndex+i] 192 } 193 194 // Make new node a child of this node 195 if i < len(path) { 196 path = path[i:] 197 c := path[0] 198 199 // '/' after param 200 if n.nType == param && c == '/' && len(n.children) == 1 { 201 parentFullPathIndex += len(n.path) 202 n = n.children[0] 203 n.priority++ 204 continue walk 205 } 206 207 // Check if a child with the next path byte exists 208 for i, max := 0, len(n.indices); i < max; i++ { 209 if c == n.indices[i] { 210 parentFullPathIndex += len(n.path) 211 i = n.incrementChildPrio(i) 212 n = n.children[i] 213 continue walk 214 } 215 } 216 217 // Otherwise insert it 218 if c != ':' && c != '*' && n.nType != catchAll { 219 // []byte for proper unicode char conversion, see #65 220 n.indices += bytesconv.BytesToString([]byte{c}) 221 child := &node{ 222 fullPath: fullPath, 223 } 224 n.addChild(child) 225 n.incrementChildPrio(len(n.indices) - 1) 226 n = child 227 } else if n.wildChild { 228 // inserting a wildcard node, need to check if it conflicts with the existing wildcard 229 n = n.children[len(n.children)-1] 230 n.priority++ 231 232 // Check if the wildcard matches 233 if len(path) >= len(n.path) && n.path == path[:len(n.path)] && 234 // Adding a child to a catchAll is not possible 235 n.nType != catchAll && 236 // Check for longer wildcard, e.g. :name and :names 237 (len(n.path) >= len(path) || path[len(n.path)] == '/') { 238 continue walk 239 } 240 241 // Wildcard conflict 242 pathSeg := path 243 if n.nType != catchAll { 244 pathSeg = strings.SplitN(pathSeg, "/", 2)[0] 245 } 246 prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path 247 panic("'" + pathSeg + 248 "' in new path '" + fullPath + 249 "' conflicts with existing wildcard '" + n.path + 250 "' in existing prefix '" + prefix + 251 "'") 252 } 253 254 n.insertChild(path, fullPath, handlers) 255 return 256 } 257 258 // Otherwise add handle to current node 259 if n.handlers != nil { 260 panic("handlers are already registered for path '" + fullPath + "'") 261 } 262 n.handlers = handlers 263 n.fullPath = fullPath 264 return 265 } 266 } 267 268 // Search for a wildcard segment and check the name for invalid characters. 269 // Returns -1 as index, if no wildcard was found. 270 func findWildcard(path string) (wildcard string, i int, valid bool) { 271 // Find start 272 for start, c := range []byte(path) { 273 // A wildcard starts with ':' (param) or '*' (catch-all) 274 if c != ':' && c != '*' { 275 continue 276 } 277 278 // Find end and check for invalid characters 279 valid = true 280 for end, c := range []byte(path[start+1:]) { 281 switch c { 282 case '/': 283 return path[start : start+1+end], start, valid 284 case ':', '*': 285 valid = false 286 } 287 } 288 return path[start:], start, valid 289 } 290 return "", -1, false 291 } 292 293 func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) { 294 for { 295 // Find prefix until first wildcard 296 wildcard, i, valid := findWildcard(path) 297 if i < 0 { // No wildcard found 298 break 299 } 300 301 // The wildcard name must only contain one ':' or '*' character 302 if !valid { 303 panic("only one wildcard per path segment is allowed, has: '" + 304 wildcard + "' in path '" + fullPath + "'") 305 } 306 307 // check if the wildcard has a name 308 if len(wildcard) < 2 { 309 panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") 310 } 311 312 if wildcard[0] == ':' { // param 313 if i > 0 { 314 // Insert prefix before the current wildcard 315 n.path = path[:i] 316 path = path[i:] 317 } 318 319 child := &node{ 320 nType: param, 321 path: wildcard, 322 fullPath: fullPath, 323 } 324 n.addChild(child) 325 n.wildChild = true 326 n = child 327 n.priority++ 328 329 // if the path doesn't end with the wildcard, then there 330 // will be another subpath starting with '/' 331 if len(wildcard) < len(path) { 332 path = path[len(wildcard):] 333 334 child := &node{ 335 priority: 1, 336 fullPath: fullPath, 337 } 338 n.addChild(child) 339 n = child 340 continue 341 } 342 343 // Otherwise we're done. Insert the handle in the new leaf 344 n.handlers = handlers 345 return 346 } 347 348 // catchAll 349 if i+len(wildcard) != len(path) { 350 panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") 351 } 352 353 if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { 354 pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0] 355 panic("catch-all wildcard '" + path + 356 "' in new path '" + fullPath + 357 "' conflicts with existing path segment '" + pathSeg + 358 "' in existing prefix '" + n.path + pathSeg + 359 "'") 360 } 361 362 // currently fixed width 1 for '/' 363 i-- 364 if path[i] != '/' { 365 panic("no / before catch-all in path '" + fullPath + "'") 366 } 367 368 n.path = path[:i] 369 370 // First node: catchAll node with empty path 371 child := &node{ 372 wildChild: true, 373 nType: catchAll, 374 fullPath: fullPath, 375 } 376 377 n.addChild(child) 378 n.indices = string('/') 379 n = child 380 n.priority++ 381 382 // second node: node holding the variable 383 child = &node{ 384 path: path[i:], 385 nType: catchAll, 386 handlers: handlers, 387 priority: 1, 388 fullPath: fullPath, 389 } 390 n.children = []*node{child} 391 392 return 393 } 394 395 // If no wildcard was found, simply insert the path and handle 396 n.path = path 397 n.handlers = handlers 398 n.fullPath = fullPath 399 } 400 401 // nodeValue holds return values of (*Node).getValue method 402 type nodeValue struct { 403 handlers HandlersChain 404 params *Params 405 tsr bool 406 fullPath string 407 } 408 409 type skippedNode struct { 410 path string 411 node *node 412 paramsCount int16 413 } 414 415 // Returns the handle registered with the given path (key). The values of 416 // wildcards are saved to a map. 417 // If no handle can be found, a TSR (trailing slash redirect) recommendation is 418 // made if a handle exists with an extra (without the) trailing slash for the 419 // given path. 420 func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) { 421 var globalParamsCount int16 422 423 walk: // Outer loop for walking the tree 424 for { 425 prefix := n.path 426 if len(path) > len(prefix) { 427 if path[:len(prefix)] == prefix { 428 path = path[len(prefix):] 429 430 // Try all the non-wildcard children first by matching the indices 431 idxc := path[0] 432 for i, c := range []byte(n.indices) { 433 if c == idxc { 434 // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild 435 if n.wildChild { 436 index := len(*skippedNodes) 437 *skippedNodes = (*skippedNodes)[:index+1] 438 (*skippedNodes)[index] = skippedNode{ 439 path: prefix + path, 440 node: &node{ 441 path: n.path, 442 wildChild: n.wildChild, 443 nType: n.nType, 444 priority: n.priority, 445 children: n.children, 446 handlers: n.handlers, 447 fullPath: n.fullPath, 448 }, 449 paramsCount: globalParamsCount, 450 } 451 } 452 453 n = n.children[i] 454 continue walk 455 } 456 } 457 458 if !n.wildChild { 459 // If the path at the end of the loop is not equal to '/' and the current node has no child nodes 460 // the current node needs to roll back to last valid skippedNode 461 if path != "/" { 462 for length := len(*skippedNodes); length > 0; length-- { 463 skippedNode := (*skippedNodes)[length-1] 464 *skippedNodes = (*skippedNodes)[:length-1] 465 if strings.HasSuffix(skippedNode.path, path) { 466 path = skippedNode.path 467 n = skippedNode.node 468 if value.params != nil { 469 *value.params = (*value.params)[:skippedNode.paramsCount] 470 } 471 globalParamsCount = skippedNode.paramsCount 472 continue walk 473 } 474 } 475 } 476 477 // Nothing found. 478 // We can recommend to redirect to the same URL without a 479 // trailing slash if a leaf exists for that path. 480 value.tsr = path == "/" && n.handlers != nil 481 return 482 } 483 484 // Handle wildcard child, which is always at the end of the array 485 n = n.children[len(n.children)-1] 486 globalParamsCount++ 487 488 switch n.nType { 489 case param: 490 // fix truncate the parameter 491 // tree_test.go line: 204 492 493 // Find param end (either '/' or path end) 494 end := 0 495 for end < len(path) && path[end] != '/' { 496 end++ 497 } 498 499 // Save param value 500 if params != nil && cap(*params) > 0 { 501 if value.params == nil { 502 value.params = params 503 } 504 // Expand slice within preallocated capacity 505 i := len(*value.params) 506 *value.params = (*value.params)[:i+1] 507 val := path[:end] 508 if unescape { 509 if v, err := url.QueryUnescape(val); err == nil { 510 val = v 511 } 512 } 513 (*value.params)[i] = Param{ 514 Key: n.path[1:], 515 Value: val, 516 } 517 } 518 519 // we need to go deeper! 520 if end < len(path) { 521 if len(n.children) > 0 { 522 path = path[end:] 523 n = n.children[0] 524 continue walk 525 } 526 527 // ... but we can't 528 value.tsr = len(path) == end+1 529 return 530 } 531 532 if value.handlers = n.handlers; value.handlers != nil { 533 value.fullPath = n.fullPath 534 return 535 } 536 if len(n.children) == 1 { 537 // No handle found. Check if a handle for this path + a 538 // trailing slash exists for TSR recommendation 539 n = n.children[0] 540 value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/") 541 } 542 return 543 544 case catchAll: 545 // Save param value 546 if params != nil { 547 if value.params == nil { 548 value.params = params 549 } 550 // Expand slice within preallocated capacity 551 i := len(*value.params) 552 *value.params = (*value.params)[:i+1] 553 val := path 554 if unescape { 555 if v, err := url.QueryUnescape(path); err == nil { 556 val = v 557 } 558 } 559 (*value.params)[i] = Param{ 560 Key: n.path[2:], 561 Value: val, 562 } 563 } 564 565 value.handlers = n.handlers 566 value.fullPath = n.fullPath 567 return 568 569 default: 570 panic("invalid node type") 571 } 572 } 573 } 574 575 if path == prefix { 576 // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node 577 // the current node needs to roll back to last valid skippedNode 578 if n.handlers == nil && path != "/" { 579 for length := len(*skippedNodes); length > 0; length-- { 580 skippedNode := (*skippedNodes)[length-1] 581 *skippedNodes = (*skippedNodes)[:length-1] 582 if strings.HasSuffix(skippedNode.path, path) { 583 path = skippedNode.path 584 n = skippedNode.node 585 if value.params != nil { 586 *value.params = (*value.params)[:skippedNode.paramsCount] 587 } 588 globalParamsCount = skippedNode.paramsCount 589 continue walk 590 } 591 } 592 // n = latestNode.children[len(latestNode.children)-1] 593 } 594 // We should have reached the node containing the handle. 595 // Check if this node has a handle registered. 596 if value.handlers = n.handlers; value.handlers != nil { 597 value.fullPath = n.fullPath 598 return 599 } 600 601 // If there is no handle for this route, but this route has a 602 // wildcard child, there must be a handle for this path with an 603 // additional trailing slash 604 if path == "/" && n.wildChild && n.nType != root { 605 value.tsr = true 606 return 607 } 608 609 if path == "/" && n.nType == static { 610 value.tsr = true 611 return 612 } 613 614 // No handle found. Check if a handle for this path + a 615 // trailing slash exists for trailing slash recommendation 616 for i, c := range []byte(n.indices) { 617 if c == '/' { 618 n = n.children[i] 619 value.tsr = (len(n.path) == 1 && n.handlers != nil) || 620 (n.nType == catchAll && n.children[0].handlers != nil) 621 return 622 } 623 } 624 625 return 626 } 627 628 // Nothing found. We can recommend to redirect to the same URL with an 629 // extra trailing slash if a leaf exists for that path 630 value.tsr = path == "/" || 631 (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && 632 path == prefix[:len(prefix)-1] && n.handlers != nil) 633 634 // roll back to last valid skippedNode 635 if !value.tsr && path != "/" { 636 for length := len(*skippedNodes); length > 0; length-- { 637 skippedNode := (*skippedNodes)[length-1] 638 *skippedNodes = (*skippedNodes)[:length-1] 639 if strings.HasSuffix(skippedNode.path, path) { 640 path = skippedNode.path 641 n = skippedNode.node 642 if value.params != nil { 643 *value.params = (*value.params)[:skippedNode.paramsCount] 644 } 645 globalParamsCount = skippedNode.paramsCount 646 continue walk 647 } 648 } 649 } 650 651 return 652 } 653 } 654 655 // Makes a case-insensitive lookup of the given path and tries to find a handler. 656 // It can optionally also fix trailing slashes. 657 // It returns the case-corrected path and a bool indicating whether the lookup 658 // was successful. 659 func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) { 660 const stackBufSize = 128 661 662 // Use a static sized buffer on the stack in the common case. 663 // If the path is too long, allocate a buffer on the heap instead. 664 buf := make([]byte, 0, stackBufSize) 665 if length := len(path) + 1; length > stackBufSize { 666 buf = make([]byte, 0, length) 667 } 668 669 ciPath := n.findCaseInsensitivePathRec( 670 path, 671 buf, // Preallocate enough memory for new path 672 [4]byte{}, // Empty rune buffer 673 fixTrailingSlash, 674 ) 675 676 return ciPath, ciPath != nil 677 } 678 679 // Shift bytes in array by n bytes left 680 func shiftNRuneBytes(rb [4]byte, n int) [4]byte { 681 switch n { 682 case 0: 683 return rb 684 case 1: 685 return [4]byte{rb[1], rb[2], rb[3], 0} 686 case 2: 687 return [4]byte{rb[2], rb[3]} 688 case 3: 689 return [4]byte{rb[3]} 690 default: 691 return [4]byte{} 692 } 693 } 694 695 // Recursive case-insensitive lookup function used by n.findCaseInsensitivePath 696 func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte { 697 npLen := len(n.path) 698 699 walk: // Outer loop for walking the tree 700 for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) { 701 // Add common prefix to result 702 oldPath := path 703 path = path[npLen:] 704 ciPath = append(ciPath, n.path...) 705 706 if len(path) == 0 { 707 // We should have reached the node containing the handle. 708 // Check if this node has a handle registered. 709 if n.handlers != nil { 710 return ciPath 711 } 712 713 // No handle found. 714 // Try to fix the path by adding a trailing slash 715 if fixTrailingSlash { 716 for i, c := range []byte(n.indices) { 717 if c == '/' { 718 n = n.children[i] 719 if (len(n.path) == 1 && n.handlers != nil) || 720 (n.nType == catchAll && n.children[0].handlers != nil) { 721 return append(ciPath, '/') 722 } 723 return nil 724 } 725 } 726 } 727 return nil 728 } 729 730 // If this node does not have a wildcard (param or catchAll) child, 731 // we can just look up the next child node and continue to walk down 732 // the tree 733 if !n.wildChild { 734 // Skip rune bytes already processed 735 rb = shiftNRuneBytes(rb, npLen) 736 737 if rb[0] != 0 { 738 // Old rune not finished 739 idxc := rb[0] 740 for i, c := range []byte(n.indices) { 741 if c == idxc { 742 // continue with child node 743 n = n.children[i] 744 npLen = len(n.path) 745 continue walk 746 } 747 } 748 } else { 749 // Process a new rune 750 var rv rune 751 752 // Find rune start. 753 // Runes are up to 4 byte long, 754 // -4 would definitely be another rune. 755 var off int 756 for max := min(npLen, 3); off < max; off++ { 757 if i := npLen - off; utf8.RuneStart(oldPath[i]) { 758 // read rune from cached path 759 rv, _ = utf8.DecodeRuneInString(oldPath[i:]) 760 break 761 } 762 } 763 764 // Calculate lowercase bytes of current rune 765 lo := unicode.ToLower(rv) 766 utf8.EncodeRune(rb[:], lo) 767 768 // Skip already processed bytes 769 rb = shiftNRuneBytes(rb, off) 770 771 idxc := rb[0] 772 for i, c := range []byte(n.indices) { 773 // Lowercase matches 774 if c == idxc { 775 // must use a recursive approach since both the 776 // uppercase byte and the lowercase byte might exist 777 // as an index 778 if out := n.children[i].findCaseInsensitivePathRec( 779 path, ciPath, rb, fixTrailingSlash, 780 ); out != nil { 781 return out 782 } 783 break 784 } 785 } 786 787 // If we found no match, the same for the uppercase rune, 788 // if it differs 789 if up := unicode.ToUpper(rv); up != lo { 790 utf8.EncodeRune(rb[:], up) 791 rb = shiftNRuneBytes(rb, off) 792 793 idxc := rb[0] 794 for i, c := range []byte(n.indices) { 795 // Uppercase matches 796 if c == idxc { 797 // Continue with child node 798 n = n.children[i] 799 npLen = len(n.path) 800 continue walk 801 } 802 } 803 } 804 } 805 806 // Nothing found. We can recommend to redirect to the same URL 807 // without a trailing slash if a leaf exists for that path 808 if fixTrailingSlash && path == "/" && n.handlers != nil { 809 return ciPath 810 } 811 return nil 812 } 813 814 n = n.children[0] 815 switch n.nType { 816 case param: 817 // Find param end (either '/' or path end) 818 end := 0 819 for end < len(path) && path[end] != '/' { 820 end++ 821 } 822 823 // Add param value to case insensitive path 824 ciPath = append(ciPath, path[:end]...) 825 826 // We need to go deeper! 827 if end < len(path) { 828 if len(n.children) > 0 { 829 // Continue with child node 830 n = n.children[0] 831 npLen = len(n.path) 832 path = path[end:] 833 continue 834 } 835 836 // ... but we can't 837 if fixTrailingSlash && len(path) == end+1 { 838 return ciPath 839 } 840 return nil 841 } 842 843 if n.handlers != nil { 844 return ciPath 845 } 846 847 if fixTrailingSlash && len(n.children) == 1 { 848 // No handle found. Check if a handle for this path + a 849 // trailing slash exists 850 n = n.children[0] 851 if n.path == "/" && n.handlers != nil { 852 return append(ciPath, '/') 853 } 854 } 855 856 return nil 857 858 case catchAll: 859 return append(ciPath, path...) 860 861 default: 862 panic("invalid node type") 863 } 864 } 865 866 // Nothing found. 867 // Try to fix the path by adding / removing a trailing slash 868 if fixTrailingSlash { 869 if path == "/" { 870 return ciPath 871 } 872 if len(path)+1 == npLen && n.path[len(path)] == '/' && 873 strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil { 874 return append(ciPath, n.path...) 875 } 876 } 877 return nil 878 }