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