| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package http | |
| import "math" | |
| // A routingIndex optimizes conflict detection by indexing patterns. | |
| // | |
| // The basic idea is to rule out patterns that cannot conflict with a given | |
| // pattern because they have a different literal in a corresponding segment. | |
| // See the comments in [routingIndex.possiblyConflictingPatterns] for more details. | |
| type routingIndex struct { | |
| // map from a particular segment position and value to all registered patterns | |
| // with that value in that position. | |
| // For example, the key {1, "b"} would hold the patterns "/a/b" and "/a/b/c" | |
| // but not "/a", "b/a", "/a/c" or "/a/{x}". | |
| segments map[routingIndexKey][]*pattern | |
| // All patterns that end in a multi wildcard (including trailing slash). | |
| // We do not try to be clever about indexing multi patterns, because there | |
| // are unlikely to be many of them. | |
| multis []*pattern | |
| } | |
| type routingIndexKey struct { | |
| pos int // 0-based segment position | |
| s string // literal, or empty for wildcard | |
| } | |
| func (idx *routingIndex) addPattern(pat *pattern) { | |
| if pat.lastSegment().multi { | |
| idx.multis = append(idx.multis, pat) | |
| } else { | |
| if idx.segments == nil { | |
| idx.segments = map[routingIndexKey][]*pattern{} | |
| } | |
| for pos, seg := range pat.segments { | |
| key := routingIndexKey{pos: pos, s: ""} | |
| if !seg.wild { | |
| key.s = seg.s | |
| } | |
| idx.segments[key] = append(idx.segments[key], pat) | |
| } | |
| } | |
| } | |
| // possiblyConflictingPatterns calls f on all patterns that might conflict with | |
| // pat. If f returns a non-nil error, possiblyConflictingPatterns returns immediately | |
| // with that error. | |
| // | |
| // To be correct, possiblyConflictingPatterns must include all patterns that | |
| // might conflict. But it may also include patterns that cannot conflict. | |
| // For instance, an implementation that returns all registered patterns is correct. | |
| // We use this fact throughout, simplifying the implementation by returning more | |
| // patterns that we might need to. | |
| func (idx *routingIndex) possiblyConflictingPatterns(pat *pattern, f func(*pattern) error) (err error) { | |
| // Terminology: | |
| // dollar pattern: one ending in "{$}" | |
| // multi pattern: one ending in a trailing slash or "{x...}" wildcard | |
| // ordinary pattern: neither of the above | |
| // apply f to all the pats, stopping on error. | |
| apply := func(pats []*pattern) error { | |
| if err != nil { | |
| return err | |
| } | |
| for _, p := range pats { | |
| err = f(p) | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| // Our simple indexing scheme doesn't try to prune multi patterns; assume | |
| // any of them can match the argument. | |
| if err := apply(idx.multis); err != nil { | |
| return err | |
| } | |
| if pat.lastSegment().s == "/" { | |
| // All paths that a dollar pattern matches end in a slash; no paths that | |
| // an ordinary pattern matches do. So only other dollar or multi | |
| // patterns can conflict with a dollar pattern. Furthermore, conflicting | |
| // dollar patterns must have the {$} in the same position. | |
| return apply(idx.segments[routingIndexKey{s: "/", pos: len(pat.segments) - 1}]) | |
| } | |
| // For ordinary and multi patterns, the only conflicts can be with a multi, | |
| // or a pattern that has the same literal or a wildcard at some literal | |
| // position. | |
| // We could intersect all the possible matches at each position, but we | |
| // do something simpler: we find the position with the fewest patterns. | |
| var lmin, wmin []*pattern | |
| min := math.MaxInt | |
| hasLit := false | |
| for i, seg := range pat.segments { | |
| if seg.multi { | |
| break | |
| } | |
| if !seg.wild { | |
| hasLit = true | |
| lpats := idx.segments[routingIndexKey{s: seg.s, pos: i}] | |
| wpats := idx.segments[routingIndexKey{s: "", pos: i}] | |
| if sum := len(lpats) + len(wpats); sum < min { | |
| lmin = lpats | |
| wmin = wpats | |
| min = sum | |
| } | |
| } | |
| } | |
| if hasLit { | |
| apply(lmin) | |
| apply(wmin) | |
| return err | |
| } | |
| // This pattern is all wildcards. | |
| // Check it against everything. | |
| for _, pats := range idx.segments { | |
| apply(pats) | |
| } | |
| return err | |
| } |