Source file src/net/http/routing_index.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http
     6  
     7  import "math"
     8  
     9  // A routingIndex optimizes conflict detection by indexing patterns.
    10  //
    11  // The basic idea is to rule out patterns that cannot conflict with a given
    12  // pattern because they have a different literal in a corresponding segment.
    13  // See the comments in [routingIndex.possiblyConflictingPatterns] for more details.
    14  type routingIndex struct {
    15  	// map from a particular segment position and value to all registered patterns
    16  	// with that value in that position.
    17  	// For example, the key {1, "b"} would hold the patterns "/a/b" and "/a/b/c"
    18  	// but not "/a", "b/a", "/a/c" or "/a/{x}".
    19  	segments map[routingIndexKey][]*pattern
    20  	// All patterns that end in a multi wildcard (including trailing slash).
    21  	// We do not try to be clever about indexing multi patterns, because there
    22  	// are unlikely to be many of them.
    23  	multis []*pattern
    24  }
    25  
    26  type routingIndexKey struct {
    27  	pos int    // 0-based segment position
    28  	s   string // literal, or empty for wildcard
    29  }
    30  
    31  func (idx *routingIndex) addPattern(pat *pattern) {
    32  	if pat.lastSegment().multi {
    33  		idx.multis = append(idx.multis, pat)
    34  	} else {
    35  		if idx.segments == nil {
    36  			idx.segments = map[routingIndexKey][]*pattern{}
    37  		}
    38  		for pos, seg := range pat.segments {
    39  			key := routingIndexKey{pos: pos, s: ""}
    40  			if !seg.wild {
    41  				key.s = seg.s
    42  			}
    43  			idx.segments[key] = append(idx.segments[key], pat)
    44  		}
    45  	}
    46  }
    47  
    48  // possiblyConflictingPatterns calls f on all patterns that might conflict with
    49  // pat. If f returns a non-nil error, possiblyConflictingPatterns returns immediately
    50  // with that error.
    51  //
    52  // To be correct, possiblyConflictingPatterns must include all patterns that
    53  // might conflict. But it may also include patterns that cannot conflict.
    54  // For instance, an implementation that returns all registered patterns is correct.
    55  // We use this fact throughout, simplifying the implementation by returning more
    56  // patterns that we might need to.
    57  func (idx *routingIndex) possiblyConflictingPatterns(pat *pattern, f func(*pattern) error) (err error) {
    58  	// Terminology:
    59  	//   dollar pattern: one ending in "{$}"
    60  	//   multi pattern: one ending in a trailing slash or "{x...}" wildcard
    61  	//   ordinary pattern: neither of the above
    62  
    63  	// apply f to all the pats, stopping on error.
    64  	apply := func(pats []*pattern) error {
    65  		if err != nil {
    66  			return err
    67  		}
    68  		for _, p := range pats {
    69  			err = f(p)
    70  			if err != nil {
    71  				return err
    72  			}
    73  		}
    74  		return nil
    75  	}
    76  
    77  	// Our simple indexing scheme doesn't try to prune multi patterns; assume
    78  	// any of them can match the argument.
    79  	if err := apply(idx.multis); err != nil {
    80  		return err
    81  	}
    82  	if pat.lastSegment().s == "/" {
    83  		// All paths that a dollar pattern matches end in a slash; no paths that
    84  		// an ordinary pattern matches do. So only other dollar or multi
    85  		// patterns can conflict with a dollar pattern. Furthermore, conflicting
    86  		// dollar patterns must have the {$} in the same position.
    87  		return apply(idx.segments[routingIndexKey{s: "/", pos: len(pat.segments) - 1}])
    88  	}
    89  	// For ordinary and multi patterns, the only conflicts can be with a multi,
    90  	// or a pattern that has the same literal or a wildcard at some literal
    91  	// position.
    92  	// We could intersect all the possible matches at each position, but we
    93  	// do something simpler: we find the position with the fewest patterns.
    94  	var lmin, wmin []*pattern
    95  	min := math.MaxInt
    96  	hasLit := false
    97  	for i, seg := range pat.segments {
    98  		if seg.multi {
    99  			break
   100  		}
   101  		if !seg.wild {
   102  			hasLit = true
   103  			lpats := idx.segments[routingIndexKey{s: seg.s, pos: i}]
   104  			wpats := idx.segments[routingIndexKey{s: "", pos: i}]
   105  			if sum := len(lpats) + len(wpats); sum < min {
   106  				lmin = lpats
   107  				wmin = wpats
   108  				min = sum
   109  			}
   110  		}
   111  	}
   112  	if hasLit {
   113  		apply(lmin)
   114  		apply(wmin)
   115  		return err
   116  	}
   117  
   118  	// This pattern is all wildcards.
   119  	// Check it against everything.
   120  	for _, pats := range idx.segments {
   121  		apply(pats)
   122  	}
   123  	return err
   124  }
   125  

View as plain text