gin-gonic / gin
Showing 3 of 5 files from the diff.

@@ -17,6 +17,7 @@
Loading
17 17
var (
18 18
	strColon = []byte(":")
19 19
	strStar  = []byte("*")
20 +
	strSlash = []byte("/")
20 21
)
21 22
22 23
// Param is a single URL parameter, consisting of a key and a value.
@@ -98,6 +99,11 @@
Loading
98 99
	return n
99 100
}
100 101
102 +
func countSections(path string) uint16 {
103 +
	s := bytesconv.StringToBytes(path)
104 +
	return uint16(bytes.Count(s, strSlash))
105 +
}
106 +
101 107
type nodeType uint8
102 108
103 109
const (
@@ -393,16 +399,19 @@
Loading
393 399
	fullPath string
394 400
}
395 401
402 +
type skippedNode struct {
403 +
	path        string
404 +
	node        *node
405 +
	paramsCount int16
406 +
}
407 +
396 408
// Returns the handle registered with the given path (key). The values of
397 409
// wildcards are saved to a map.
398 410
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
399 411
// made if a handle exists with an extra (without the) trailing slash for the
400 412
// given path.
401 -
func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
402 -
	var (
403 -
		skippedPath string
404 -
		latestNode  = n // Caching the latest node
405 -
	)
413 +
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
414 +
	var globalParamsCount int16
406 415
407 416
walk: // Outer loop for walking the tree
408 417
	for {
@@ -417,15 +426,20 @@
Loading
417 426
					if c == idxc {
418 427
						//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
419 428
						if n.wildChild {
420 -
							skippedPath = prefix + path
421 -
							latestNode = &node{
422 -
								path:      n.path,
423 -
								wildChild: n.wildChild,
424 -
								nType:     n.nType,
425 -
								priority:  n.priority,
426 -
								children:  n.children,
427 -
								handlers:  n.handlers,
428 -
								fullPath:  n.fullPath,
429 +
							index := len(*skippedNodes)
430 +
							*skippedNodes = (*skippedNodes)[:index+1]
431 +
							(*skippedNodes)[index] = skippedNode{
432 +
								path: prefix + path,
433 +
								node: &node{
434 +
									path:      n.path,
435 +
									wildChild: n.wildChild,
436 +
									nType:     n.nType,
437 +
									priority:  n.priority,
438 +
									children:  n.children,
439 +
									handlers:  n.handlers,
440 +
									fullPath:  n.fullPath,
441 +
								},
442 +
								paramsCount: globalParamsCount,
429 443
							}
430 444
						}
431 445
@@ -434,10 +448,22 @@
Loading
434 448
					}
435 449
				}
436 450
				// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
437 -
				// the current node needs to be equal to the latest matching node
438 -
				matched := path != "/" && !n.wildChild
439 -
				if matched {
440 -
					n = latestNode
451 +
				// the current node needs to roll back to last vaild skippedNode
452 +
453 +
				if path != "/" && !n.wildChild {
454 +
					for l := len(*skippedNodes); l > 0; {
455 +
						skippedNode := (*skippedNodes)[l-1]
456 +
						*skippedNodes = (*skippedNodes)[:l-1]
457 +
						if strings.HasSuffix(skippedNode.path, path) {
458 +
							path = skippedNode.path
459 +
							n = skippedNode.node
460 +
							if value.params != nil {
461 +
								*value.params = (*value.params)[:skippedNode.paramsCount]
462 +
							}
463 +
							globalParamsCount = skippedNode.paramsCount
464 +
							continue walk
465 +
						}
466 +
					}
441 467
				}
442 468
443 469
				// If there is no wildcard pattern, recommend a redirection
@@ -451,18 +477,12 @@
Loading
451 477
452 478
				// Handle wildcard child, which is always at the end of the array
453 479
				n = n.children[len(n.children)-1]
480 +
				globalParamsCount++
454 481
455 482
				switch n.nType {
456 483
				case param:
457 484
					// fix truncate the parameter
458 485
					// tree_test.go  line: 204
459 -
					if matched {
460 -
						path = prefix + path
461 -
						// The saved path is used after the prefix route is intercepted by matching
462 -
						if n.indices == "/" {
463 -
							path = skippedPath[1:]
464 -
						}
465 -
					}
466 486
467 487
					// Find param end (either '/' or path end)
468 488
					end := 0
@@ -548,9 +568,22 @@
Loading
548 568
549 569
		if path == prefix {
550 570
			// 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
551 -
			// the current node needs to be equal to the latest matching node
552 -
			if latestNode.wildChild && n.handlers == nil && path != "/" {
553 -
				n = latestNode.children[len(latestNode.children)-1]
571 +
			// the current node needs to roll back to last vaild skippedNode
572 +
			if n.handlers == nil && path != "/" {
573 +
				for l := len(*skippedNodes); l > 0; {
574 +
					skippedNode := (*skippedNodes)[l-1]
575 +
					*skippedNodes = (*skippedNodes)[:l-1]
576 +
					if strings.HasSuffix(skippedNode.path, path) {
577 +
						path = skippedNode.path
578 +
						n = skippedNode.node
579 +
						if value.params != nil {
580 +
							*value.params = (*value.params)[:skippedNode.paramsCount]
581 +
						}
582 +
						globalParamsCount = skippedNode.paramsCount
583 +
						continue walk
584 +
					}
585 +
				}
586 +
				//	n = latestNode.children[len(latestNode.children)-1]
554 587
			}
555 588
			// We should have reached the node containing the handle.
556 589
			// Check if this node has a handle registered.
@@ -581,19 +614,21 @@
Loading
581 614
			return
582 615
		}
583 616
584 -
		if path != "/" && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) {
585 -
			path = skippedPath
586 -
			// Reduce the number of cycles
587 -
			n, latestNode = latestNode, n
588 -
			// skippedPath cannot execute
589 -
			// example:
590 -
			// * /:cc/cc
591 -
			// call /a/cc 	     expectations:match/200      Actual:match/200
592 -
			// call /a/dd 	     expectations:unmatch/404    Actual: panic
593 -
			// call /addr/dd/aa  expectations:unmatch/404    Actual: panic
594 -
			// skippedPath: It can only be executed if the secondary route is not found
595 -
			skippedPath = ""
596 -
			continue walk
617 +
		// roll back to last vaild skippedNode
618 +
		if path != "/" {
619 +
			for l := len(*skippedNodes); l > 0; {
620 +
				skippedNode := (*skippedNodes)[l-1]
621 +
				*skippedNodes = (*skippedNodes)[:l-1]
622 +
				if strings.HasSuffix(skippedNode.path, path) {
623 +
					path = skippedNode.path
624 +
					n = skippedNode.node
625 +
					if value.params != nil {
626 +
						*value.params = (*value.params)[:skippedNode.paramsCount]
627 +
					}
628 +
					globalParamsCount = skippedNode.paramsCount
629 +
					continue walk
630 +
				}
631 +
			}
597 632
		}
598 633
599 634
		// Nothing found. We can recommend to redirect to the same URL with an

@@ -144,6 +144,7 @@
Loading
144 144
	pool             sync.Pool
145 145
	trees            methodTrees
146 146
	maxParams        uint16
147 +
	maxSections      uint16
147 148
	trustedProxies   []string
148 149
	trustedCIDRs     []*net.IPNet
149 150
}
@@ -200,7 +201,8 @@
Loading
200 201
201 202
func (engine *Engine) allocateContext() *Context {
202 203
	v := make(Params, 0, engine.maxParams)
203 -
	return &Context{engine: engine, params: &v}
204 +
	skippedNodes := make([]skippedNode, 0, engine.maxSections)
205 +
	return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
204 206
}
205 207
206 208
// Delims sets template left and right delims and returns a Engine instance.
@@ -306,6 +308,10 @@
Loading
306 308
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
307 309
		engine.maxParams = paramsCount
308 310
	}
311 +
312 +
	if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
313 +
		engine.maxSections = sectionsCount
314 +
	}
309 315
}
310 316
311 317
// Routes returns a slice of registered routes, including some useful information, such as:
@@ -539,7 +545,7 @@
Loading
539 545
		}
540 546
		root := t[i].root
541 547
		// Find route in tree
542 -
		value := root.getValue(rPath, c.params, unescape)
548 +
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
543 549
		if value.params != nil {
544 550
			c.Params = *value.params
545 551
		}
@@ -567,7 +573,7 @@
Loading
567 573
			if tree.method == httpMethod {
568 574
				continue
569 575
			}
570 -
			if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
576 +
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
571 577
				c.handlers = engine.allNoMethod
572 578
				serveError(c, http.StatusMethodNotAllowed, default405Body)
573 579
				return

@@ -55,8 +55,9 @@
Loading
55 55
	index    int8
56 56
	fullPath string
57 57
58 -
	engine *Engine
59 -
	params *Params
58 +
	engine       *Engine
59 +
	params       *Params
60 +
	skippedNodes *[]skippedNode
60 61
61 62
	// This mutex protect Keys map
62 63
	mu sync.RWMutex
@@ -99,6 +100,7 @@
Loading
99 100
	c.queryCache = nil
100 101
	c.formCache = nil
101 102
	*c.params = (*c.params)[:0]
103 +
	*c.skippedNodes = (*c.skippedNodes)[:0]
102 104
}
103 105
104 106
// Copy returns a copy of the current context that can be safely used outside the request's scope.
Files Coverage
binding 100.00%
render 92.61%
auth.go 100.00%
context.go 97.83%
debug.go 100.00%
deprecated.go 100.00%
errors.go 100.00%
fs.go 100.00%
gin.go 99.12%
logger.go 100.00%
mode.go 100.00%
path.go 100.00%
recovery.go 98.08%
response_writer.go 93.48%
routergroup.go 100.00%
test_helpers.go 100.00%
tree.go 100.00%
utils.go 96.81%
Project Totals (41 files) 98.74%
1374668782
nomsgpack go-1.13 macos-latest
1374668782
go-1.14 ubuntu-latest
1374668782
go-1.14 macos-latest
1374668782
go-1.13 macos-latest
1374668782
go-1.16 macos-latest
1374668782
nomsgpack go-1.15 macos-latest
1374668782
go-1.17 macos-latest
1374668782
nomsgpack go-1.16 macos-latest
1374668782
nomsgpack go-1.14 macos-latest
1374668782
nomsgpack go-1.17 macos-latest
1374668782
nomsgpack go-1.16 ubuntu-latest
1374668782
nomsgpack go-1.15 ubuntu-latest
1374668782
nomsgpack go-1.13 ubuntu-latest
1374668782
nomsgpack go-1.17 ubuntu-latest
1374668782
go-1.15 ubuntu-latest
1374668782
go-1.17 ubuntu-latest
1374668782
go-1.13 ubuntu-latest
1374668782
nomsgpack go-1.14 ubuntu-latest
1374668782
go-1.15 macos-latest
1374668782
go-1.16 ubuntu-latest
1
coverage:
2
  notify:
3
    gitter:
4
      default:
5
        url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading