// 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.// Patterns for ServeMux routing.package httpimport ()// A pattern is something that can be matched against an HTTP request.// It has an optional method, an optional host, and a path.type pattern struct { str string// original string method string host string// The representation of a path differs from the surface syntax, which // simplifies most algorithms. // // Paths ending in '/' are represented with an anonymous "..." wildcard. // For example, the path "a/" is represented as a literal segment "a" followed // by a segment with multi==true. // // Paths ending in "{$}" are represented with the literal segment "/". // For example, the path "a/{$}" is represented as a literal segment "a" followed // by a literal segment "/". segments []segment loc string// source location of registering call, for helpful messages}func ( *pattern) () string { return .str }func ( *pattern) () segment {return .segments[len(.segments)-1]}// A segment is a pattern piece that matches one or more path segments, or// a trailing slash.//// If wild is false, it matches a literal segment, or, if s == "/", a trailing slash.// Examples://// "a" => segment{s: "a"}// "/{$}" => segment{s: "/"}//// If wild is true and multi is false, it matches a single path segment.// Example://// "{x}" => segment{s: "x", wild: true}//// If both wild and multi are true, it matches all remaining path segments.// Example://// "{rest...}" => segment{s: "rest", wild: true, multi: true}type segment struct { s string// literal or wildcard name or "/" for "/{$}". wild bool multi bool// "..." wildcard}// parsePattern parses a string into a Pattern.// The string's syntax is//// [METHOD] [HOST]/[PATH]//// where:// - METHOD is an HTTP method// - HOST is a hostname// - PATH consists of slash-separated segments, where each segment is either// a literal or a wildcard of the form "{name}", "{name...}", or "{$}".//// METHOD, HOST and PATH are all optional; that is, the string can be "/".// If METHOD is present, it must be followed by at least one space or tab.// Wildcard names must be valid Go identifiers.// The "{$}" and "{name...}" wildcard must occur at the end of PATH.// PATH may end with a '/'.// Wildcard names in a path must be distinct.func parsePattern( string) ( *pattern, error) {iflen() == 0 {returnnil, errors.New("empty pattern") } := 0// offset into stringdeferfunc() {if != nil { = fmt.Errorf("at offset %d: %w", , ) } }() , , := , "", falseif := strings.IndexAny(, " \t"); >= 0 { , , = [:], strings.TrimLeft([+1:], " \t"), true }if ! { = = "" }if != "" && !validMethod() {returnnil, fmt.Errorf("invalid method %q", ) } := &pattern{str: , method: }if { = len() + 1 } := strings.IndexByte(, '/')if < 0 {returnnil, errors.New("host/path missing /") } .host = [:] = [:]if := strings.IndexByte(.host, '{'); >= 0 { += returnnil, errors.New("host contains '{' (missing initial '/'?)") }// At this point, rest is the path. += // An unclean path with a method that is not CONNECT can never match, // because paths are cleaned before matching.if != "" && != "CONNECT" && != cleanPath() {returnnil, errors.New("non-CONNECT pattern with unclean path can never match") } := map[string]bool{} // remember wildcard names to catch dupsforlen() > 0 {// Invariant: rest[0] == '/'. = [1:] = len() - len()iflen() == 0 {// Trailing slash. .segments = append(.segments, segment{wild: true, multi: true})break } := strings.IndexByte(, '/')if < 0 { = len() }varstring , = [:], [:]if := strings.IndexByte(, '{'); < 0 {// Literal. = pathUnescape() .segments = append(.segments, segment{s: }) } else {// Wildcard.if != 0 {returnnil, errors.New("bad wildcard segment (must start with '{')") }if [len()-1] != '}' {returnnil, errors.New("bad wildcard segment (must end with '}')") } := [1 : len()-1]if == "$" {iflen() != 0 {returnnil, errors.New("{$} not at end") } .segments = append(.segments, segment{s: "/"})break } , := strings.CutSuffix(, "...")if && len() != 0 {returnnil, errors.New("{...} wildcard not at end") }if == "" {returnnil, errors.New("empty wildcard") }if !isValidWildcardName() {returnnil, fmt.Errorf("bad wildcard name %q", ) }if [] {returnnil, fmt.Errorf("duplicate wildcard name %q", ) } [] = true .segments = append(.segments, segment{s: , wild: true, multi: }) } }return , nil}func isValidWildcardName( string) bool {if == "" {returnfalse }// Valid Go identifier.for , := range {if !unicode.IsLetter() && != '_' && ( == 0 || !unicode.IsDigit()) {returnfalse } }returntrue}func pathUnescape( string) string { , := url.PathUnescape()if != nil {// Invalidly escaped path; use the originalreturn }return}// relationship is a relationship between two patterns, p1 and p2.type relationship stringconst ( equivalent relationship = "equivalent"// both match the same requests moreGeneral relationship = "moreGeneral"// p1 matches everything p2 does & more moreSpecific relationship = "moreSpecific"// p2 matches everything p1 does & more disjoint relationship = "disjoint"// there is no request that both match overlaps relationship = "overlaps"// there is a request that both match, but neither is more specific)// conflictsWith reports whether p1 conflicts with p2, that is, whether// there is a request that both match but where neither is higher precedence// than the other.//// Precedence is defined by two rules:// 1. Patterns with a host win over patterns without a host.// 2. Patterns whose method and path is more specific win. One pattern is more// specific than another if the second matches all the (method, path) pairs// of the first and more.//// If rule 1 doesn't apply, then two patterns conflict if their relationship// is either equivalence (they match the same set of requests) or overlap// (they both match some requests, but neither is more specific than the other).func ( *pattern) ( *pattern) bool {if .host != .host {// Either one host is empty and the other isn't, in which case the // one with the host wins by rule 1, or neither host is empty // and they differ, so they won't match the same paths.returnfalse } := .comparePathsAndMethods()return == equivalent || == overlaps}func ( *pattern) ( *pattern) relationship { := .compareMethods()// Optimization: avoid a call to comparePaths.if == disjoint {returndisjoint } := .comparePaths()returncombineRelationships(, )}// compareMethods determines the relationship between the method// part of patterns p1 and p2.//// A method can either be empty, "GET", or something else.// The empty string matches any method, so it is the most general.// "GET" matches both GET and HEAD.// Anything else matches only itself.func ( *pattern) ( *pattern) relationship {if .method == .method {returnequivalent }if .method == "" {// p1 matches any method, but p2 does not, so p1 is more general.returnmoreGeneral }if .method == "" {returnmoreSpecific }if .method == "GET" && .method == "HEAD" {// p1 matches GET and HEAD; p2 matches only HEAD.returnmoreGeneral }if .method == "GET" && .method == "HEAD" {returnmoreSpecific }returndisjoint}// comparePaths determines the relationship between the path// part of two patterns.func ( *pattern) ( *pattern) relationship {// Optimization: if a path pattern doesn't end in a multi ("...") wildcard, then it // can only match paths with the same number of segments.iflen(.segments) != len(.segments) && !.lastSegment().multi && !.lastSegment().multi {returndisjoint }// Consider corresponding segments in the two path patterns.var , []segment := equivalentfor , = .segments, .segments; len() > 0 && len() > 0; , = [1:], [1:] { = combineRelationships(, compareSegments([0], [0]))if == disjoint {return } }// We've reached the end of the corresponding segments of the patterns. // If they have the same number of segments, then we've already determined // their relationship.iflen() == 0 && len() == 0 {return }// Otherwise, the only way they could fail to be disjoint is if the shorter // pattern ends in a multi. In that case, that multi is more general // than the remainder of the longer pattern, so combine those two relationships.iflen() < len() && .lastSegment().multi {returncombineRelationships(, moreGeneral) }iflen() < len() && .lastSegment().multi {returncombineRelationships(, moreSpecific) }returndisjoint}// compareSegments determines the relationship between two segments.func compareSegments(, segment) relationship {if .multi && .multi {returnequivalent }if .multi {returnmoreGeneral }if .multi {returnmoreSpecific }if .wild && .wild {returnequivalent }if .wild {if .s == "/" {// A single wildcard doesn't match a trailing slash.returndisjoint }returnmoreGeneral }if .wild {if .s == "/" {returndisjoint }returnmoreSpecific }// Both literals.if .s == .s {returnequivalent }returndisjoint}// combineRelationships determines the overall relationship of two patterns// given the relationships of a partition of the patterns into two parts.//// For example, if p1 is more general than p2 in one way but equivalent// in the other, then it is more general overall.//// Or if p1 is more general in one way and more specific in the other, then// they overlap.func combineRelationships(, relationship) relationship {switch {caseequivalent:returncasedisjoint:returndisjointcaseoverlaps:if == disjoint {returndisjoint }returnoverlapscasemoreGeneral, moreSpecific:switch {caseequivalent:returncaseinverseRelationship():returnoverlapsdefault:return }default:panic(fmt.Sprintf("unknown relationship %q", )) }}// If p1 has relationship `r` to p2, then// p2 has inverseRelationship(r) to p1.func inverseRelationship( relationship) relationship {switch {casemoreSpecific:returnmoreGeneralcasemoreGeneral:returnmoreSpecificdefault:return }}// isLitOrSingle reports whether the segment is a non-dollar literal or a single wildcard.func isLitOrSingle( segment) bool {if .wild {return !.multi }return .s != "/"}// describeConflict returns an explanation of why two patterns conflict.func describeConflict(, *pattern) string { := .compareMethods() := .comparePaths() := combineRelationships(, )if == equivalent {returnfmt.Sprintf("%s matches the same requests as %s", , ) }if != overlaps {panic("describeConflict called with non-conflicting patterns") }if == overlaps {returnfmt.Sprintf(`%[1]s and %[2]s both match some paths, like %[3]q.But neither is more specific than the other.%[1]s matches %[4]q, but %[2]s doesn't.%[2]s matches %[5]q, but %[1]s doesn't.`, , , commonPath(, ), differencePath(, ), differencePath(, )) }if == moreGeneral && == moreSpecific {returnfmt.Sprintf("%s matches more methods than %s, but has a more specific path pattern", , ) }if == moreSpecific && == moreGeneral {returnfmt.Sprintf("%s matches fewer methods than %s, but has a more general path pattern", , ) }returnfmt.Sprintf("bug: unexpected way for two patterns %s and %s to conflict: methods %s, paths %s", , , , )}// writeMatchingPath writes to b a path that matches the segments.func writeMatchingPath( *strings.Builder, []segment) {for , := range {writeSegment(, ) }}func writeSegment( *strings.Builder, segment) { .WriteByte('/')if !.multi && .s != "/" { .WriteString(.s) }}// commonPath returns a path that both p1 and p2 match.// It assumes there is such a path.func commonPath(, *pattern) string {varstrings.Buildervar , []segmentfor , = .segments, .segments; len() > 0 && len() > 0; , = [1:], [1:] {if := [0]; .wild {writeSegment(&, [0]) } else {writeSegment(&, ) } }iflen() > 0 {writeMatchingPath(&, ) } elseiflen() > 0 {writeMatchingPath(&, ) }return .String()}// differencePath returns a path that p1 matches and p2 doesn't.// It assumes there is such a path.func differencePath(, *pattern) string {varstrings.Buildervar , []segmentfor , = .segments, .segments; len() > 0 && len() > 0; , = [1:], [1:] { := [0] := [0]if .multi && .multi {// From here the patterns match the same paths, so we must have found a difference earlier. .WriteByte('/')return .String() }if .multi && !.multi {// s1 ends in a "..." wildcard but s2 does not. // A trailing slash will distinguish them, unless s2 ends in "{$}", // in which case any segment will do; prefer the wildcard name if // it has one. .WriteByte('/')if .s == "/" {if .s != "" { .WriteString(.s) } else { .WriteString("x") } }return .String() }if !.multi && .multi {writeSegment(&, ) } elseif .wild && .wild {// Both patterns will match whatever we put here; use // the first wildcard name.writeSegment(&, ) } elseif .wild && !.wild {// s1 is a wildcard, s2 is a literal. // Any segment other than s2.s will work. // Prefer the wildcard name, but if it's the same as the literal, // tweak the literal.if .s != .s {writeSegment(&, ) } else { .WriteByte('/') .WriteString(.s + "x") } } elseif !.wild && .wild {writeSegment(&, ) } else {// Both are literals. A precondition of this function is that the // patterns overlap, so they must be the same literal. Use it.if .s != .s {panic(fmt.Sprintf("literals differ: %q and %q", .s, .s)) }writeSegment(&, ) } }iflen() > 0 {// p1 is longer than p2, and p2 does not end in a multi. // Anything that matches the rest of p1 will do.writeMatchingPath(&, ) } elseiflen() > 0 {writeMatchingPath(&, ) }return .String()}
The pages are generated with Goldsv0.7.3-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.