github.com/cloudwego/hertz@v0.9.3/pkg/route/tree.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * The MIT License (MIT) 17 * 18 * Copyright (c) 2021 LabStack 19 * 20 * Permission is hereby granted, free of charge, to any person obtaining a copy 21 * of this software and associated documentation files (the "Software"), to deal 22 * in the Software without restriction, including without limitation the rights 23 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 * copies of the Software, and to permit persons to whom the Software is 25 * furnished to do so, subject to the following conditions: 26 * 27 * The above copyright notice and this permission notice shall be included in 28 * all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 * THE SOFTWARE. 37 * 38 * This file may have been modified by CloudWeGo authors. All CloudWeGo 39 * Modifications are Copyright 2022 CloudWeGo Authors. 40 */ 41 42 package route 43 44 import ( 45 "bytes" 46 "fmt" 47 "net/url" 48 "strings" 49 "unicode" 50 51 "github.com/cloudwego/hertz/internal/bytesconv" 52 "github.com/cloudwego/hertz/internal/bytestr" 53 "github.com/cloudwego/hertz/pkg/app" 54 "github.com/cloudwego/hertz/pkg/route/param" 55 ) 56 57 type router struct { 58 method string 59 root *node 60 hasTsrHandler map[string]bool 61 } 62 63 type MethodTrees []*router 64 65 func (trees MethodTrees) get(method string) *router { 66 for _, tree := range trees { 67 if tree.method == method { 68 return tree 69 } 70 } 71 return nil 72 } 73 74 func countParams(path string) uint16 { 75 var n uint16 76 s := bytesconv.S2b(path) 77 n += uint16(bytes.Count(s, bytestr.StrColon)) 78 n += uint16(bytes.Count(s, bytestr.StrStar)) 79 return n 80 } 81 82 type ( 83 node struct { 84 kind kind 85 label byte 86 prefix string 87 parent *node 88 children children 89 // original path 90 ppath string 91 // param names 92 pnames []string 93 handlers app.HandlersChain 94 paramChild *node 95 anyChild *node 96 // isLeaf indicates that node does not have child routes 97 isLeaf bool 98 } 99 kind uint8 100 children []*node 101 ) 102 103 const ( 104 // static kind 105 skind kind = iota 106 // param kind 107 pkind 108 // all kind 109 akind 110 paramLabel = byte(':') 111 anyLabel = byte('*') 112 slash = "/" 113 nilString = "" 114 ) 115 116 func checkPathValid(path string) { 117 if path == nilString { 118 panic("empty path") 119 } 120 if path[0] != '/' { 121 panic("path must begin with '/'") 122 } 123 for i, c := range []byte(path) { 124 switch c { 125 case ':': 126 if (i < len(path)-1 && path[i+1] == '/') || i == (len(path)-1) { 127 panic("wildcards must be named with a non-empty name in path '" + path + "'") 128 } 129 i++ 130 for ; i < len(path) && path[i] != '/'; i++ { 131 if path[i] == ':' || path[i] == '*' { 132 panic("only one wildcard per path segment is allowed, find multi in path '" + path + "'") 133 } 134 } 135 case '*': 136 if i == len(path)-1 { 137 panic("wildcards must be named with a non-empty name in path '" + path + "'") 138 } 139 if i > 0 && path[i-1] != '/' { 140 panic(" no / before wildcards in path " + path) 141 } 142 for ; i < len(path); i++ { 143 if path[i] == '/' { 144 panic("catch-all routes are only allowed at the end of the path in path '" + path + "'") 145 } 146 } 147 } 148 } 149 } 150 151 // addRoute adds a node with the given handle to the path. 152 func (r *router) addRoute(path string, h app.HandlersChain) { 153 checkPathValid(path) 154 155 var ( 156 pnames []string // Param names 157 ppath = path // Pristine path 158 ) 159 160 if h == nil { 161 panic(fmt.Sprintf("Adding route without handler function: %v", path)) 162 } 163 164 // Add the front static route part of a non-static route 165 for i, lcpIndex := 0, len(path); i < lcpIndex; i++ { 166 // param route 167 if path[i] == paramLabel { 168 j := i + 1 169 170 r.insert(path[:i], nil, skind, nilString, nil) 171 for ; i < lcpIndex && path[i] != '/'; i++ { 172 } 173 174 pnames = append(pnames, path[j:i]) 175 path = path[:j] + path[i:] 176 i, lcpIndex = j, len(path) 177 178 if i == lcpIndex { 179 // path node is last fragment of route path. ie. `/users/:id` 180 r.insert(path[:i], h, pkind, ppath, pnames) 181 return 182 } else { 183 r.insert(path[:i], nil, pkind, nilString, pnames) 184 } 185 } else if path[i] == anyLabel { 186 r.insert(path[:i], nil, skind, nilString, nil) 187 pnames = append(pnames, path[i+1:]) 188 r.insert(path[:i+1], h, akind, ppath, pnames) 189 return 190 } 191 } 192 193 r.insert(path, h, skind, ppath, pnames) 194 } 195 196 func (r *router) insert(path string, h app.HandlersChain, t kind, ppath string, pnames []string) { 197 currentNode := r.root 198 if currentNode == nil { 199 panic("hertz: invalid node") 200 } 201 search := path 202 203 for { 204 searchLen := len(search) 205 prefixLen := len(currentNode.prefix) 206 lcpLen := 0 207 208 max := prefixLen 209 if searchLen < max { 210 max = searchLen 211 } 212 for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ { 213 } 214 215 if lcpLen == 0 { 216 // At root node 217 currentNode.label = search[0] 218 currentNode.prefix = search 219 if h != nil { 220 currentNode.kind = t 221 currentNode.handlers = h 222 currentNode.ppath = ppath 223 currentNode.pnames = pnames 224 } 225 currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil 226 } else if lcpLen < prefixLen { 227 // Split node 228 n := newNode( 229 currentNode.kind, 230 currentNode.prefix[lcpLen:], 231 currentNode, 232 currentNode.children, 233 currentNode.handlers, 234 currentNode.ppath, 235 currentNode.pnames, 236 currentNode.paramChild, 237 currentNode.anyChild, 238 ) 239 // Update parent path for all children to new node 240 for _, child := range currentNode.children { 241 child.parent = n 242 } 243 if currentNode.paramChild != nil { 244 currentNode.paramChild.parent = n 245 } 246 if currentNode.anyChild != nil { 247 currentNode.anyChild.parent = n 248 } 249 250 // Reset parent node 251 currentNode.kind = skind 252 currentNode.label = currentNode.prefix[0] 253 currentNode.prefix = currentNode.prefix[:lcpLen] 254 currentNode.children = nil 255 currentNode.handlers = nil 256 currentNode.ppath = nilString 257 currentNode.pnames = nil 258 currentNode.paramChild = nil 259 currentNode.anyChild = nil 260 currentNode.isLeaf = false 261 262 // Only Static children could reach here 263 currentNode.children = append(currentNode.children, n) 264 265 if lcpLen == searchLen { 266 // At parent node 267 currentNode.kind = t 268 currentNode.handlers = h 269 currentNode.ppath = ppath 270 currentNode.pnames = pnames 271 } else { 272 // Create child node 273 n = newNode(t, search[lcpLen:], currentNode, nil, h, ppath, pnames, nil, nil) 274 // Only Static children could reach here 275 currentNode.children = append(currentNode.children, n) 276 } 277 currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil 278 } else if lcpLen < searchLen { 279 search = search[lcpLen:] 280 c := currentNode.findChildWithLabel(search[0]) 281 if c != nil { 282 // Go deeper 283 currentNode = c 284 continue 285 } 286 // Create child node 287 n := newNode(t, search, currentNode, nil, h, ppath, pnames, nil, nil) 288 switch t { 289 case skind: 290 currentNode.children = append(currentNode.children, n) 291 case pkind: 292 currentNode.paramChild = n 293 case akind: 294 currentNode.anyChild = n 295 } 296 currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil 297 } else { 298 // Node already exists 299 if currentNode.handlers != nil && h != nil { 300 panic("handlers are already registered for path '" + ppath + "'") 301 } 302 303 if h != nil { 304 currentNode.handlers = h 305 currentNode.ppath = ppath 306 currentNode.pnames = pnames 307 } 308 } 309 return 310 } 311 } 312 313 // find finds registered handler by method and path, parses URL params and puts params to context 314 func (r *router) find(path string, paramsPointer *param.Params, unescape bool) (res nodeValue) { 315 var ( 316 cn = r.root // current node 317 search = path // current path 318 searchIndex = 0 319 buf []byte 320 paramIndex int 321 ) 322 323 backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) { 324 previous := cn 325 cn = previous.parent 326 valid = cn != nil 327 328 // Next node type by priority 329 if previous.kind == akind { 330 nextNodeKind = skind 331 } else { 332 nextNodeKind = previous.kind + 1 333 } 334 335 if fromKind == skind { 336 // when backtracking is done from static kind block we did not change search so nothing to restore 337 return 338 } 339 340 // restore search to value it was before we move to current node we are backtracking from. 341 if previous.kind == skind { 342 searchIndex -= len(previous.prefix) 343 } else { 344 paramIndex-- 345 // for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue 346 // for that index as it would also contain part of path we cut off before moving into node we are backtracking from 347 searchIndex -= len((*paramsPointer)[paramIndex].Value) 348 (*paramsPointer) = (*paramsPointer)[:paramIndex] 349 } 350 search = path[searchIndex:] 351 return 352 } 353 354 // search order: static > param > any 355 for { 356 if cn.kind == skind { 357 if len(search) >= len(cn.prefix) && cn.prefix == search[:len(cn.prefix)] { 358 // Continue search 359 search = search[len(cn.prefix):] 360 searchIndex = searchIndex + len(cn.prefix) 361 } else { 362 // not equal 363 if (len(cn.prefix) == len(search)+1) && 364 (cn.prefix[len(search)]) == '/' && cn.prefix[:len(search)] == search && (cn.handlers != nil || cn.anyChild != nil) { 365 res.tsr = true 366 } 367 // No matching prefix, let's backtrack to the first possible alternative node of the decision path 368 nk, ok := backtrackToNextNodeKind(skind) 369 if !ok { 370 return // No other possibilities on the decision path 371 } else if nk == pkind { 372 goto Param 373 } else { 374 // Not found (this should never be possible for static node we are looking currently) 375 break 376 } 377 } 378 } 379 if search == nilString && len(cn.handlers) != 0 { 380 res.handlers = cn.handlers 381 break 382 } 383 384 // Static node 385 if search != nilString { 386 // If it can execute that logic, there is handler registered on the current node and search is `/`. 387 if search == "/" && cn.handlers != nil { 388 res.tsr = true 389 } 390 if child := cn.findChild(search[0]); child != nil { 391 cn = child 392 continue 393 } 394 } 395 396 if search == nilString { 397 if cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) { 398 res.tsr = true 399 } 400 } 401 402 Param: 403 // Param node 404 if child := cn.paramChild; search != nilString && child != nil { 405 cn = child 406 i := strings.Index(search, slash) 407 if i == -1 { 408 i = len(search) 409 } 410 (*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)] 411 val := search[:i] 412 if unescape { 413 if v, err := url.QueryUnescape(search[:i]); err == nil { 414 val = v 415 } 416 } 417 (*paramsPointer)[paramIndex].Value = val 418 paramIndex++ 419 search = search[i:] 420 searchIndex = searchIndex + i 421 if search == nilString { 422 if cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) { 423 res.tsr = true 424 } 425 } 426 continue 427 } 428 Any: 429 // Any node 430 if child := cn.anyChild; child != nil { 431 // If any node is found, use remaining path for paramValues 432 cn = child 433 (*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)] 434 index := len(cn.pnames) - 1 435 val := search 436 if unescape { 437 if v, err := url.QueryUnescape(search); err == nil { 438 val = v 439 } 440 } 441 442 (*paramsPointer)[index].Value = bytesconv.B2s(append(buf, val...)) 443 // update indexes/search in case we need to backtrack when no handler match is found 444 paramIndex++ 445 searchIndex += len(search) 446 search = nilString 447 res.handlers = cn.handlers 448 break 449 } 450 451 // Let's backtrack to the first possible alternative node of the decision path 452 nk, ok := backtrackToNextNodeKind(akind) 453 if !ok { 454 break // No other possibilities on the decision path 455 } else if nk == pkind { 456 goto Param 457 } else if nk == akind { 458 goto Any 459 } else { 460 // Not found 461 break 462 } 463 } 464 465 if cn != nil { 466 res.fullPath = cn.ppath 467 for i, name := range cn.pnames { 468 (*paramsPointer)[i].Key = name 469 } 470 } 471 472 return 473 } 474 475 func (n *node) findChild(l byte) *node { 476 for _, c := range n.children { 477 if c.label == l { 478 return c 479 } 480 } 481 return nil 482 } 483 484 func (n *node) findChildWithLabel(l byte) *node { 485 for _, c := range n.children { 486 if c.label == l { 487 return c 488 } 489 } 490 if l == paramLabel { 491 return n.paramChild 492 } 493 if l == anyLabel { 494 return n.anyChild 495 } 496 return nil 497 } 498 499 func newNode(t kind, pre string, p *node, child children, mh app.HandlersChain, ppath string, pnames []string, paramChildren, anyChildren *node) *node { 500 return &node{ 501 kind: t, 502 label: pre[0], 503 prefix: pre, 504 parent: p, 505 children: child, 506 ppath: ppath, 507 pnames: pnames, 508 handlers: mh, 509 paramChild: paramChildren, 510 anyChild: anyChildren, 511 isLeaf: child == nil && paramChildren == nil && anyChildren == nil, 512 } 513 } 514 515 // nodeValue holds return values of (*Node).getValue method 516 type nodeValue struct { 517 handlers app.HandlersChain 518 tsr bool 519 fullPath string 520 } 521 522 // Makes a case-insensitive lookup of the given path and tries to find a handler. 523 // It returns the case-corrected path and a bool indicating whether the lookup 524 // was successful. 525 func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { 526 ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory 527 // Match paramKind. 528 if n.label == paramLabel { 529 end := 0 530 for end < len(path) && path[end] != '/' { 531 end++ 532 } 533 ciPath = append(ciPath, path[:end]...) 534 if end < len(path) { 535 if len(n.children) > 0 { 536 path = path[end:] 537 538 goto loop 539 } 540 541 if fixTrailingSlash && len(path) == end+1 { 542 return ciPath, true 543 } 544 return 545 } 546 547 if n.handlers != nil { 548 return ciPath, true 549 } 550 if fixTrailingSlash && len(n.children) == 1 { 551 // No handle found. Check if a handle for this path with(without) a trailing slash exists 552 n = n.children[0] 553 if n.prefix == "/" && n.handlers != nil { 554 return append(ciPath, '/'), true 555 } 556 } 557 return 558 } 559 560 // Match allKind. 561 if n.label == anyLabel { 562 return append(ciPath, path...), true 563 } 564 565 // Match static kind. 566 if len(path) >= len(n.prefix) && strings.EqualFold(path[:len(n.prefix)], n.prefix) { 567 path = path[len(n.prefix):] 568 ciPath = append(ciPath, n.prefix...) 569 570 if len(path) == 0 { 571 if n.handlers != nil { 572 return ciPath, true 573 } 574 // No handle found. 575 // Try to fix the path by adding a trailing slash. 576 if fixTrailingSlash { 577 for i := 0; i < len(n.children); i++ { 578 if n.children[i].label == '/' { 579 n = n.children[i] 580 if (len(n.prefix) == 1 && n.handlers != nil) || 581 (n.prefix == "*" && n.children[0].handlers != nil) { 582 return append(ciPath, '/'), true 583 } 584 return 585 } 586 } 587 } 588 return 589 } 590 } else if fixTrailingSlash { 591 // Nothing found. 592 // Try to fix the path by adding / removing a trailing slash. 593 if path == "/" { 594 return ciPath, true 595 } 596 if len(path)+1 == len(n.prefix) && n.prefix[len(path)] == '/' && 597 strings.EqualFold(path, n.prefix[:len(path)]) && 598 n.handlers != nil { 599 return append(ciPath, n.prefix...), true 600 } 601 } 602 603 loop: 604 // First match static kind. 605 for _, node := range n.children { 606 if unicode.ToLower(rune(path[0])) == unicode.ToLower(rune(node.label)) { 607 out, found := node.findCaseInsensitivePath(path, fixTrailingSlash) 608 if found { 609 return append(ciPath, out...), true 610 } 611 } 612 } 613 614 if n.paramChild != nil { 615 out, found := n.paramChild.findCaseInsensitivePath(path, fixTrailingSlash) 616 if found { 617 return append(ciPath, out...), true 618 } 619 } 620 621 if n.anyChild != nil { 622 out, found := n.anyChild.findCaseInsensitivePath(path, fixTrailingSlash) 623 if found { 624 return append(ciPath, out...), true 625 } 626 } 627 628 // Nothing found. We can recommend to redirect to the same URL 629 // without a trailing slash if a leaf exists for that path 630 found = fixTrailingSlash && path == "/" && n.handlers != nil 631 return 632 }