github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/api/server/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 // in the LICENSE file. 4 5 package server 6 7 import ( 8 "net/http" 9 "strings" 10 "unicode" 11 "unicode/utf8" 12 ) 13 14 type Handle func(http.ResponseWriter, *http.Request, Params) 15 type Param struct { 16 Key string 17 Value string 18 } 19 type Params []Param 20 21 func min(a, b int) int { 22 if a <= b { 23 return a 24 } 25 return b 26 } 27 28 func countParams(path string) uint8 { 29 var n uint 30 for i := 0; i < len(path); i++ { 31 if path[i] != ':' && path[i] != '*' { 32 continue 33 } 34 n++ 35 } 36 if n >= 255 { 37 return 255 38 } 39 return uint8(n) 40 } 41 42 type nodeType uint8 43 44 const ( 45 static nodeType = iota // default 46 root 47 param 48 catchAll 49 ) 50 51 type node struct { 52 path string 53 wildChild bool 54 nType nodeType 55 maxParams uint8 56 indices string 57 children []*node 58 handle Handle 59 priority uint32 60 } 61 62 // increments priority of the given child and reorders if necessary 63 func (n *node) incrementChildPrio(pos int) int { 64 n.children[pos].priority++ 65 prio := n.children[pos].priority 66 67 // adjust position (move to front) 68 newPos := pos 69 for newPos > 0 && n.children[newPos-1].priority < prio { 70 // swap node positions 71 tmpN := n.children[newPos-1] 72 n.children[newPos-1] = n.children[newPos] 73 n.children[newPos] = tmpN 74 75 newPos-- 76 } 77 78 // build new index char string 79 if newPos != pos { 80 n.indices = n.indices[:newPos] + // unchanged prefix, might be empty 81 n.indices[pos:pos+1] + // the index char we move 82 n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' 83 } 84 85 return newPos 86 } 87 88 // addRoute adds a node with the given handle to the path. 89 // Not concurrency-safe! 90 func (n *node) addRoute(path string, handle Handle) { 91 fullPath := path 92 n.priority++ 93 numParams := countParams(path) 94 95 // non-empty tree 96 if len(n.path) > 0 || len(n.children) > 0 { 97 walk: 98 for { 99 // Update maxParams of the current node 100 if numParams > n.maxParams { 101 n.maxParams = numParams 102 } 103 104 // Find the longest common prefix. 105 // This also implies that the common prefix contains no ':' or '*' 106 // since the existing key can't contain those chars. 107 i := 0 108 max := min(len(path), len(n.path)) 109 for i < max && path[i] == n.path[i] { 110 i++ 111 } 112 113 // Split edge 114 if i < len(n.path) { 115 child := node{ 116 path: n.path[i:], 117 wildChild: n.wildChild, 118 nType: static, 119 indices: n.indices, 120 children: n.children, 121 handle: n.handle, 122 priority: n.priority - 1, 123 } 124 125 // Update maxParams (max of all children) 126 for i := range child.children { 127 if child.children[i].maxParams > child.maxParams { 128 child.maxParams = child.children[i].maxParams 129 } 130 } 131 132 n.children = []*node{&child} 133 // []byte for proper unicode char conversion, see #65 134 n.indices = string([]byte{n.path[i]}) 135 n.path = path[:i] 136 n.handle = nil 137 n.wildChild = false 138 } 139 140 // Make new node a child of this node 141 if i < len(path) { 142 path = path[i:] 143 144 if n.wildChild { 145 n = n.children[0] 146 n.priority++ 147 148 // Update maxParams of the child node 149 if numParams > n.maxParams { 150 n.maxParams = numParams 151 } 152 numParams-- 153 154 // Check if the wildcard matches 155 if len(path) >= len(n.path) && n.path == path[:len(n.path)] { 156 // check for longer wildcard, e.g. :name and :names 157 if len(n.path) >= len(path) || path[len(n.path)] == '/' { 158 continue walk 159 } 160 } 161 162 panic("path segment '" + path + 163 "' conflicts with existing wildcard '" + n.path + 164 "' in path '" + fullPath + "'") 165 } 166 167 c := path[0] 168 169 // slash after param 170 if n.nType == param && c == '/' && len(n.children) == 1 { 171 n = n.children[0] 172 n.priority++ 173 continue walk 174 } 175 176 // Check if a child with the next path byte exists 177 for i := 0; i < len(n.indices); i++ { 178 if c == n.indices[i] { 179 i = n.incrementChildPrio(i) 180 n = n.children[i] 181 continue walk 182 } 183 } 184 185 // Otherwise insert it 186 if c != ':' && c != '*' { 187 // []byte for proper unicode char conversion, see #65 188 n.indices += string([]byte{c}) 189 child := &node{ 190 maxParams: numParams, 191 } 192 n.children = append(n.children, child) 193 n.incrementChildPrio(len(n.indices) - 1) 194 n = child 195 } 196 n.insertChild(numParams, path, fullPath, handle) 197 return 198 199 } else if i == len(path) { // Make node a (in-path) leaf 200 if n.handle != nil { 201 panic("a handle is already registered for path '" + fullPath + "'") 202 } 203 n.handle = handle 204 } 205 return 206 } 207 } else { // Empty tree 208 n.insertChild(numParams, path, fullPath, handle) 209 n.nType = root 210 } 211 } 212 213 func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) { 214 var offset int // already handled bytes of the path 215 216 // find prefix until first wildcard (beginning with ':'' or '*'') 217 for i, max := 0, len(path); numParams > 0; i++ { 218 c := path[i] 219 if c != ':' && c != '*' { 220 continue 221 } 222 223 // find wildcard end (either '/' or path end) 224 end := i + 1 225 for end < max && path[end] != '/' { 226 switch path[end] { 227 // the wildcard name must not contain ':' and '*' 228 case ':', '*': 229 panic("only one wildcard per path segment is allowed, has: '" + 230 path[i:] + "' in path '" + fullPath + "'") 231 default: 232 end++ 233 } 234 } 235 236 // check if this Node existing children which would be 237 // unreachable if we insert the wildcard here 238 if len(n.children) > 0 { 239 panic("wildcard route '" + path[i:end] + 240 "' conflicts with existing children in path '" + fullPath + "'") 241 } 242 243 // check if the wildcard has a name 244 if end-i < 2 { 245 panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") 246 } 247 248 if c == ':' { // param 249 // split path at the beginning of the wildcard 250 if i > 0 { 251 n.path = path[offset:i] 252 offset = i 253 } 254 255 child := &node{ 256 nType: param, 257 maxParams: numParams, 258 } 259 n.children = []*node{child} 260 n.wildChild = true 261 n = child 262 n.priority++ 263 numParams-- 264 265 // if the path doesn't end with the wildcard, then there 266 // will be another non-wildcard subpath starting with '/' 267 if end < max { 268 n.path = path[offset:end] 269 offset = end 270 271 child := &node{ 272 maxParams: numParams, 273 priority: 1, 274 } 275 n.children = []*node{child} 276 n = child 277 } 278 279 } else { // catchAll 280 if end != max || numParams > 1 { 281 panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") 282 } 283 284 if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { 285 panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") 286 } 287 288 // currently fixed width 1 for '/' 289 i-- 290 if path[i] != '/' { 291 panic("no / before catch-all in path '" + fullPath + "'") 292 } 293 294 n.path = path[offset:i] 295 296 // first node: catchAll node with empty path 297 child := &node{ 298 wildChild: true, 299 nType: catchAll, 300 maxParams: 1, 301 } 302 n.children = []*node{child} 303 n.indices = string(path[i]) 304 n = child 305 n.priority++ 306 307 // second node: node holding the variable 308 child = &node{ 309 path: path[i:], 310 nType: catchAll, 311 maxParams: 1, 312 handle: handle, 313 priority: 1, 314 } 315 n.children = []*node{child} 316 317 return 318 } 319 } 320 321 // insert remaining path part and handle to the leaf 322 n.path = path[offset:] 323 n.handle = handle 324 } 325 326 // Returns the handle registered with the given path (key). The values of 327 // wildcards are saved to a map. 328 // If no handle can be found, a TSR (trailing slash redirect) recommendation is 329 // made if a handle exists with an extra (without the) trailing slash for the 330 // given path. 331 func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) { 332 walk: // outer loop for walking the tree 333 for { 334 if len(path) > len(n.path) { 335 if path[:len(n.path)] == n.path { 336 path = path[len(n.path):] 337 // If this node does not have a wildcard (param or catchAll) 338 // child, we can just look up the next child node and continue 339 // to walk down the tree 340 if !n.wildChild { 341 c := path[0] 342 for i := 0; i < len(n.indices); i++ { 343 if c == n.indices[i] { 344 n = n.children[i] 345 continue walk 346 } 347 } 348 349 // Nothing found. 350 // We can recommend to redirect to the same URL without a 351 // trailing slash if a leaf exists for that path. 352 tsr = (path == "/" && n.handle != nil) 353 return 354 355 } 356 357 // handle wildcard child 358 n = n.children[0] 359 switch n.nType { 360 case param: 361 // find param end (either '/' or path end) 362 end := 0 363 for end < len(path) && path[end] != '/' { 364 end++ 365 } 366 367 // save param value 368 if p == nil { 369 // lazy allocation 370 p = make(Params, 0, n.maxParams) 371 } 372 i := len(p) 373 p = p[:i+1] // expand slice within preallocated capacity 374 p[i].Key = n.path[1:] 375 p[i].Value = path[:end] 376 377 // we need to go deeper! 378 if end < len(path) { 379 if len(n.children) > 0 { 380 path = path[end:] 381 n = n.children[0] 382 continue walk 383 } 384 385 // ... but we can't 386 tsr = (len(path) == end+1) 387 return 388 } 389 390 if handle = n.handle; handle != nil { 391 return 392 } else if len(n.children) == 1 { 393 // No handle found. Check if a handle for this path + a 394 // trailing slash exists for TSR recommendation 395 n = n.children[0] 396 tsr = (n.path == "/" && n.handle != nil) 397 } 398 399 return 400 401 case catchAll: 402 // save param value 403 if p == nil { 404 // lazy allocation 405 p = make(Params, 0, n.maxParams) 406 } 407 i := len(p) 408 p = p[:i+1] // expand slice within preallocated capacity 409 p[i].Key = n.path[2:] 410 p[i].Value = path 411 412 handle = n.handle 413 return 414 415 default: 416 panic("invalid node type") 417 } 418 } 419 } else if path == n.path { 420 // We should have reached the node containing the handle. 421 // Check if this node has a handle registered. 422 if handle = n.handle; handle != nil { 423 return 424 } 425 426 if path == "/" && n.wildChild && n.nType != root { 427 tsr = true 428 return 429 } 430 431 // No handle found. Check if a handle for this path + a 432 // trailing slash exists for trailing slash recommendation 433 for i := 0; i < len(n.indices); i++ { 434 if n.indices[i] == '/' { 435 n = n.children[i] 436 tsr = (len(n.path) == 1 && n.handle != nil) || 437 (n.nType == catchAll && n.children[0].handle != nil) 438 return 439 } 440 } 441 442 return 443 } 444 445 // Nothing found. We can recommend to redirect to the same URL with an 446 // extra trailing slash if a leaf exists for that path 447 tsr = (path == "/") || 448 (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && 449 path == n.path[:len(n.path)-1] && n.handle != nil) 450 return 451 } 452 } 453 454 // Makes a case-insensitive lookup of the given path and tries to find a handler. 455 // It can optionally also fix trailing slashes. 456 // It returns the case-corrected path and a bool indicating whether the lookup 457 // was successful. 458 func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { 459 return n.findCaseInsensitivePathRec( 460 path, 461 strings.ToLower(path), 462 make([]byte, 0, len(path)+1), // preallocate enough memory for new path 463 [4]byte{}, // empty rune buffer 464 fixTrailingSlash, 465 ) 466 } 467 468 // shift bytes in array by n bytes left 469 func shiftNRuneBytes(rb [4]byte, n int) [4]byte { 470 switch n { 471 case 0: 472 return rb 473 case 1: 474 return [4]byte{rb[1], rb[2], rb[3], 0} 475 case 2: 476 return [4]byte{rb[2], rb[3]} 477 case 3: 478 return [4]byte{rb[3]} 479 default: 480 return [4]byte{} 481 } 482 } 483 484 // recursive case-insensitive lookup function used by n.findCaseInsensitivePath 485 func (n *node) findCaseInsensitivePathRec(path, loPath string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) { 486 loNPath := strings.ToLower(n.path) 487 488 walk: // outer loop for walking the tree 489 for len(loPath) >= len(loNPath) && (len(loNPath) == 0 || loPath[1:len(loNPath)] == loNPath[1:]) { 490 // add common path to result 491 ciPath = append(ciPath, n.path...) 492 493 if path = path[len(n.path):]; len(path) > 0 { 494 loOld := loPath 495 loPath = loPath[len(loNPath):] 496 497 // If this node does not have a wildcard (param or catchAll) child, 498 // we can just look up the next child node and continue to walk down 499 // the tree 500 if !n.wildChild { 501 // skip rune bytes already processed 502 rb = shiftNRuneBytes(rb, len(loNPath)) 503 504 if rb[0] != 0 { 505 // old rune not finished 506 for i := 0; i < len(n.indices); i++ { 507 if n.indices[i] == rb[0] { 508 // continue with child node 509 n = n.children[i] 510 loNPath = strings.ToLower(n.path) 511 continue walk 512 } 513 } 514 } else { 515 // process a new rune 516 var rv rune 517 518 // find rune start 519 // runes are up to 4 byte long, 520 // -4 would definitely be another rune 521 var off int 522 for max := min(len(loNPath), 3); off < max; off++ { 523 if i := len(loNPath) - off; utf8.RuneStart(loOld[i]) { 524 // read rune from cached lowercase path 525 rv, _ = utf8.DecodeRuneInString(loOld[i:]) 526 break 527 } 528 } 529 530 // calculate lowercase bytes of current rune 531 utf8.EncodeRune(rb[:], rv) 532 // skipp already processed bytes 533 rb = shiftNRuneBytes(rb, off) 534 535 for i := 0; i < len(n.indices); i++ { 536 // lowercase matches 537 if n.indices[i] == rb[0] { 538 // must use a recursive approach since both the 539 // uppercase byte and the lowercase byte might exist 540 // as an index 541 if out, found := n.children[i].findCaseInsensitivePathRec( 542 path, loPath, ciPath, rb, fixTrailingSlash, 543 ); found { 544 return out, true 545 } 546 break 547 } 548 } 549 550 // same for uppercase rune, if it differs 551 if up := unicode.ToUpper(rv); up != rv { 552 utf8.EncodeRune(rb[:], up) 553 rb = shiftNRuneBytes(rb, off) 554 555 for i := 0; i < len(n.indices); i++ { 556 // uppercase matches 557 if n.indices[i] == rb[0] { 558 // continue with child node 559 n = n.children[i] 560 loNPath = strings.ToLower(n.path) 561 continue walk 562 } 563 } 564 } 565 } 566 567 // Nothing found. We can recommend to redirect to the same URL 568 // without a trailing slash if a leaf exists for that path 569 return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil) 570 } 571 572 n = n.children[0] 573 switch n.nType { 574 case param: 575 // find param end (either '/' or path end) 576 k := 0 577 for k < len(path) && path[k] != '/' { 578 k++ 579 } 580 581 // add param value to case insensitive path 582 ciPath = append(ciPath, path[:k]...) 583 584 // we need to go deeper! 585 if k < len(path) { 586 if len(n.children) > 0 { 587 // continue with child node 588 n = n.children[0] 589 loNPath = strings.ToLower(n.path) 590 loPath = loPath[k:] 591 path = path[k:] 592 continue 593 } 594 595 // ... but we can't 596 if fixTrailingSlash && len(path) == k+1 { 597 return ciPath, true 598 } 599 return ciPath, false 600 } 601 602 if n.handle != nil { 603 return ciPath, true 604 } else if fixTrailingSlash && len(n.children) == 1 { 605 // No handle found. Check if a handle for this path + a 606 // trailing slash exists 607 n = n.children[0] 608 if n.path == "/" && n.handle != nil { 609 return append(ciPath, '/'), true 610 } 611 } 612 return ciPath, false 613 614 case catchAll: 615 return append(ciPath, path...), true 616 617 default: 618 panic("invalid node type") 619 } 620 } else { 621 // We should have reached the node containing the handle. 622 // Check if this node has a handle registered. 623 if n.handle != nil { 624 return ciPath, true 625 } 626 627 // No handle found. 628 // Try to fix the path by adding a trailing slash 629 if fixTrailingSlash { 630 for i := 0; i < len(n.indices); i++ { 631 if n.indices[i] == '/' { 632 n = n.children[i] 633 if (len(n.path) == 1 && n.handle != nil) || 634 (n.nType == catchAll && n.children[0].handle != nil) { 635 return append(ciPath, '/'), true 636 } 637 return ciPath, false 638 } 639 } 640 } 641 return ciPath, false 642 } 643 } 644 645 // Nothing found. 646 // Try to fix the path by adding / removing a trailing slash 647 if fixTrailingSlash { 648 if path == "/" { 649 return ciPath, true 650 } 651 if len(loPath)+1 == len(loNPath) && loNPath[len(loPath)] == '/' && 652 loPath[1:] == loNPath[1:len(loPath)] && n.handle != nil { 653 return append(ciPath, n.path...), true 654 } 655 } 656 return ciPath, false 657 }