Source File
routing_tree.go
Belonging Package
net/http
// 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.
// This file implements a decision tree for fast matching of requests to
// patterns.
//
// The root of the tree branches on the host of the request.
// The next level branches on the method.
// The remaining levels branch on consecutive segments of the path.
//
// The "more specific wins" precedence rule can result in backtracking.
// For example, given the patterns
// /a/b/z
// /a/{x}/c
// we will first try to match the path "/a/b/c" with /a/b/z, and
// when that fails we will try against /a/{x}/c.
package http
import (
)
// A routingNode is a node in the decision tree.
// The same struct is used for leaf and interior nodes.
type routingNode struct {
// A leaf node holds a single pattern and the Handler it was registered
// with.
pattern *pattern
handler Handler
// An interior node maps parts of the incoming request to child nodes.
// special children keys:
// "/" trailing slash (resulting from {$})
// "" single wildcard
children mapping[string, *routingNode]
multiChild *routingNode // child with multi wildcard
emptyChild *routingNode // optimization: child with key ""
}
// addPattern adds a pattern and its associated Handler to the tree
// at root.
func ( *routingNode) ( *pattern, Handler) {
// First level of tree is host.
:= .addChild(.host)
// Second level of tree is method.
= .addChild(.method)
// Remaining levels are path.
.addSegments(.segments, , )
}
// addSegments adds the given segments to the tree rooted at n.
// If there are no segments, then n is a leaf node that holds
// the given pattern and handler.
func ( *routingNode) ( []segment, *pattern, Handler) {
if len() == 0 {
.set(, )
return
}
:= [0]
if .multi {
if len() != 1 {
panic("multi wildcard not last")
}
:= &routingNode{}
.multiChild =
.set(, )
} else if .wild {
.addChild("").([1:], , )
} else {
.addChild(.s).([1:], , )
}
}
// set sets the pattern and handler for n, which
// must be a leaf node.
func ( *routingNode) ( *pattern, Handler) {
if .pattern != nil || .handler != nil {
panic("non-nil leaf fields")
}
.pattern =
.handler =
}
// addChild adds a child node with the given key to n
// if one does not exist, and returns the child.
func ( *routingNode) ( string) *routingNode {
if == "" {
if .emptyChild == nil {
.emptyChild = &routingNode{}
}
return .emptyChild
}
if := .findChild(); != nil {
return
}
:= &routingNode{}
.children.add(, )
return
}
// findChild returns the child of n with the given key, or nil
// if there is no child with that key.
func ( *routingNode) ( string) *routingNode {
if == "" {
return .emptyChild
}
, := .children.find()
return
}
// match returns the leaf node under root that matches the arguments, and a list
// of values for pattern wildcards in the order that the wildcards appear.
// For example, if the request path is "/a/b/c" and the pattern is "/{x}/b/{y}",
// then the second return value will be []string{"a", "c"}.
func ( *routingNode) (, , string) (*routingNode, []string) {
if != "" {
// There is a host. If there is a pattern that specifies that host and it
// matches, we are done. If the pattern doesn't match, fall through to
// try patterns with no host.
if , := .findChild().matchMethodAndPath(, ); != nil {
return ,
}
}
return .emptyChild.matchMethodAndPath(, )
}
// matchMethodAndPath matches the method and path.
// Its return values are the same as [routingNode.match].
// The receiver should be a child of the root.
func ( *routingNode) (, string) (*routingNode, []string) {
if == nil {
return nil, nil
}
if , := .findChild().matchPath(, nil); != nil {
// Exact match of method name.
return ,
}
if == "HEAD" {
// GET matches HEAD too.
if , := .findChild("GET").matchPath(, nil); != nil {
return ,
}
}
// No exact match; try patterns with no method.
return .emptyChild.matchPath(, nil)
}
// matchPath matches a path.
// Its return values are the same as [routingNode.match].
// matchPath calls itself recursively. The matches argument holds the wildcard matches
// found so far.
func ( *routingNode) ( string, []string) (*routingNode, []string) {
if == nil {
return nil, nil
}
// If path is empty, then we are done.
// If n is a leaf node, we found a match; return it.
// If n is an interior node (which means it has a nil pattern),
// then we failed to match.
if == "" {
if .pattern == nil {
return nil, nil
}
return ,
}
// Get the first segment of path.
, := firstSegment()
// First try matching against patterns that have a literal for this position.
// We know by construction that such patterns are more specific than those
// with a wildcard at this position (they are either more specific, equivalent,
// or overlap, and we ruled out the first two when the patterns were registered).
if , := .findChild().(, ); != nil {
return ,
}
// If matching a literal fails, try again with patterns that have a single
// wildcard (represented by an empty string in the child mapping).
// Again, by construction, patterns with a single wildcard must be more specific than
// those with a multi wildcard.
// We skip this step if the segment is a trailing slash, because single wildcards
// don't match trailing slashes.
if != "/" {
if , := .emptyChild.(, append(, )); != nil {
return ,
}
}
// Lastly, match the pattern (there can be at most one) that has a multi
// wildcard in this position to the rest of the path.
if := .multiChild; != nil {
// Don't record a match for a nameless wildcard (which arises from a
// trailing slash in the pattern).
if .pattern.lastSegment().s != "" {
= append(, pathUnescape([1:])) // remove initial slash
}
return ,
}
return nil, nil
}
// firstSegment splits path into its first segment, and the rest.
// The path must begin with "/".
// If path consists of only a slash, firstSegment returns ("/", "").
// The segment is returned unescaped, if possible.
func firstSegment( string) (, string) {
if == "/" {
return "/", ""
}
= [1:] // drop initial slash
:= strings.IndexByte(, '/')
if < 0 {
= len()
}
return pathUnescape([:]), [:]
}
// matchingMethods adds to methodSet all the methods that would result in a
// match if passed to routingNode.match with the given host and path.
func ( *routingNode) (, string, map[string]bool) {
if != "" {
.findChild().matchingMethodsPath(, )
}
.emptyChild.matchingMethodsPath(, )
if ["GET"] {
["HEAD"] = true
}
}
func ( *routingNode) ( string, map[string]bool) {
if == nil {
return
}
.children.eachPair(func( string, *routingNode) bool {
if , := .matchPath(, nil); != nil {
[] = true
}
return true
})
// Don't look at the empty child. If there were an empty
// child, it would match on any method, but we only
// call this when we fail to match on a method.
}
The pages are generated with Golds v0.7.0-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. |