github.com/cloudwego/hertz@v0.9.3/pkg/route/tree.go (about)

     1  /*
     2   * Copyright 2022 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * The MIT License (MIT)
    17   *
    18   * Copyright (c) 2021 LabStack
    19   *
    20   * Permission is hereby granted, free of charge, to any person obtaining a copy
    21   * of this software and associated documentation files (the "Software"), to deal
    22   * in the Software without restriction, including without limitation the rights
    23   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    24   * copies of the Software, and to permit persons to whom the Software is
    25   * furnished to do so, subject to the following conditions:
    26   *
    27   * The above copyright notice and this permission notice shall be included in
    28   * all copies or substantial portions of the Software.
    29   *
    30   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    31   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    32   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    33   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    34   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    35   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    36   * THE SOFTWARE.
    37   *
    38   * This file may have been modified by CloudWeGo authors. All CloudWeGo
    39   * Modifications are Copyright 2022 CloudWeGo Authors.
    40   */
    41  
    42  package route
    43  
    44  import (
    45  	"bytes"
    46  	"fmt"
    47  	"net/url"
    48  	"strings"
    49  	"unicode"
    50  
    51  	"github.com/cloudwego/hertz/internal/bytesconv"
    52  	"github.com/cloudwego/hertz/internal/bytestr"
    53  	"github.com/cloudwego/hertz/pkg/app"
    54  	"github.com/cloudwego/hertz/pkg/route/param"
    55  )
    56  
    57  type router struct {
    58  	method        string
    59  	root          *node
    60  	hasTsrHandler map[string]bool
    61  }
    62  
    63  type MethodTrees []*router
    64  
    65  func (trees MethodTrees) get(method string) *router {
    66  	for _, tree := range trees {
    67  		if tree.method == method {
    68  			return tree
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  func countParams(path string) uint16 {
    75  	var n uint16
    76  	s := bytesconv.S2b(path)
    77  	n += uint16(bytes.Count(s, bytestr.StrColon))
    78  	n += uint16(bytes.Count(s, bytestr.StrStar))
    79  	return n
    80  }
    81  
    82  type (
    83  	node struct {
    84  		kind     kind
    85  		label    byte
    86  		prefix   string
    87  		parent   *node
    88  		children children
    89  		// original path
    90  		ppath string
    91  		// param names
    92  		pnames     []string
    93  		handlers   app.HandlersChain
    94  		paramChild *node
    95  		anyChild   *node
    96  		// isLeaf indicates that node does not have child routes
    97  		isLeaf bool
    98  	}
    99  	kind     uint8
   100  	children []*node
   101  )
   102  
   103  const (
   104  	// static kind
   105  	skind kind = iota
   106  	// param kind
   107  	pkind
   108  	// all kind
   109  	akind
   110  	paramLabel = byte(':')
   111  	anyLabel   = byte('*')
   112  	slash      = "/"
   113  	nilString  = ""
   114  )
   115  
   116  func checkPathValid(path string) {
   117  	if path == nilString {
   118  		panic("empty path")
   119  	}
   120  	if path[0] != '/' {
   121  		panic("path must begin with '/'")
   122  	}
   123  	for i, c := range []byte(path) {
   124  		switch c {
   125  		case ':':
   126  			if (i < len(path)-1 && path[i+1] == '/') || i == (len(path)-1) {
   127  				panic("wildcards must be named with a non-empty name in path '" + path + "'")
   128  			}
   129  			i++
   130  			for ; i < len(path) && path[i] != '/'; i++ {
   131  				if path[i] == ':' || path[i] == '*' {
   132  					panic("only one wildcard per path segment is allowed, find multi in path '" + path + "'")
   133  				}
   134  			}
   135  		case '*':
   136  			if i == len(path)-1 {
   137  				panic("wildcards must be named with a non-empty name in path '" + path + "'")
   138  			}
   139  			if i > 0 && path[i-1] != '/' {
   140  				panic(" no / before wildcards in path " + path)
   141  			}
   142  			for ; i < len(path); i++ {
   143  				if path[i] == '/' {
   144  					panic("catch-all routes are only allowed at the end of the path in path '" + path + "'")
   145  				}
   146  			}
   147  		}
   148  	}
   149  }
   150  
   151  // addRoute adds a node with the given handle to the path.
   152  func (r *router) addRoute(path string, h app.HandlersChain) {
   153  	checkPathValid(path)
   154  
   155  	var (
   156  		pnames []string // Param names
   157  		ppath  = path   // Pristine path
   158  	)
   159  
   160  	if h == nil {
   161  		panic(fmt.Sprintf("Adding route without handler function: %v", path))
   162  	}
   163  
   164  	// Add the front static route part of a non-static route
   165  	for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
   166  		// param route
   167  		if path[i] == paramLabel {
   168  			j := i + 1
   169  
   170  			r.insert(path[:i], nil, skind, nilString, nil)
   171  			for ; i < lcpIndex && path[i] != '/'; i++ {
   172  			}
   173  
   174  			pnames = append(pnames, path[j:i])
   175  			path = path[:j] + path[i:]
   176  			i, lcpIndex = j, len(path)
   177  
   178  			if i == lcpIndex {
   179  				// path node is last fragment of route path. ie. `/users/:id`
   180  				r.insert(path[:i], h, pkind, ppath, pnames)
   181  				return
   182  			} else {
   183  				r.insert(path[:i], nil, pkind, nilString, pnames)
   184  			}
   185  		} else if path[i] == anyLabel {
   186  			r.insert(path[:i], nil, skind, nilString, nil)
   187  			pnames = append(pnames, path[i+1:])
   188  			r.insert(path[:i+1], h, akind, ppath, pnames)
   189  			return
   190  		}
   191  	}
   192  
   193  	r.insert(path, h, skind, ppath, pnames)
   194  }
   195  
   196  func (r *router) insert(path string, h app.HandlersChain, t kind, ppath string, pnames []string) {
   197  	currentNode := r.root
   198  	if currentNode == nil {
   199  		panic("hertz: invalid node")
   200  	}
   201  	search := path
   202  
   203  	for {
   204  		searchLen := len(search)
   205  		prefixLen := len(currentNode.prefix)
   206  		lcpLen := 0
   207  
   208  		max := prefixLen
   209  		if searchLen < max {
   210  			max = searchLen
   211  		}
   212  		for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
   213  		}
   214  
   215  		if lcpLen == 0 {
   216  			// At root node
   217  			currentNode.label = search[0]
   218  			currentNode.prefix = search
   219  			if h != nil {
   220  				currentNode.kind = t
   221  				currentNode.handlers = h
   222  				currentNode.ppath = ppath
   223  				currentNode.pnames = pnames
   224  			}
   225  			currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
   226  		} else if lcpLen < prefixLen {
   227  			// Split node
   228  			n := newNode(
   229  				currentNode.kind,
   230  				currentNode.prefix[lcpLen:],
   231  				currentNode,
   232  				currentNode.children,
   233  				currentNode.handlers,
   234  				currentNode.ppath,
   235  				currentNode.pnames,
   236  				currentNode.paramChild,
   237  				currentNode.anyChild,
   238  			)
   239  			// Update parent path for all children to new node
   240  			for _, child := range currentNode.children {
   241  				child.parent = n
   242  			}
   243  			if currentNode.paramChild != nil {
   244  				currentNode.paramChild.parent = n
   245  			}
   246  			if currentNode.anyChild != nil {
   247  				currentNode.anyChild.parent = n
   248  			}
   249  
   250  			// Reset parent node
   251  			currentNode.kind = skind
   252  			currentNode.label = currentNode.prefix[0]
   253  			currentNode.prefix = currentNode.prefix[:lcpLen]
   254  			currentNode.children = nil
   255  			currentNode.handlers = nil
   256  			currentNode.ppath = nilString
   257  			currentNode.pnames = nil
   258  			currentNode.paramChild = nil
   259  			currentNode.anyChild = nil
   260  			currentNode.isLeaf = false
   261  
   262  			// Only Static children could reach here
   263  			currentNode.children = append(currentNode.children, n)
   264  
   265  			if lcpLen == searchLen {
   266  				// At parent node
   267  				currentNode.kind = t
   268  				currentNode.handlers = h
   269  				currentNode.ppath = ppath
   270  				currentNode.pnames = pnames
   271  			} else {
   272  				// Create child node
   273  				n = newNode(t, search[lcpLen:], currentNode, nil, h, ppath, pnames, nil, nil)
   274  				// Only Static children could reach here
   275  				currentNode.children = append(currentNode.children, n)
   276  			}
   277  			currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
   278  		} else if lcpLen < searchLen {
   279  			search = search[lcpLen:]
   280  			c := currentNode.findChildWithLabel(search[0])
   281  			if c != nil {
   282  				// Go deeper
   283  				currentNode = c
   284  				continue
   285  			}
   286  			// Create child node
   287  			n := newNode(t, search, currentNode, nil, h, ppath, pnames, nil, nil)
   288  			switch t {
   289  			case skind:
   290  				currentNode.children = append(currentNode.children, n)
   291  			case pkind:
   292  				currentNode.paramChild = n
   293  			case akind:
   294  				currentNode.anyChild = n
   295  			}
   296  			currentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
   297  		} else {
   298  			// Node already exists
   299  			if currentNode.handlers != nil && h != nil {
   300  				panic("handlers are already registered for path '" + ppath + "'")
   301  			}
   302  
   303  			if h != nil {
   304  				currentNode.handlers = h
   305  				currentNode.ppath = ppath
   306  				currentNode.pnames = pnames
   307  			}
   308  		}
   309  		return
   310  	}
   311  }
   312  
   313  // find finds registered handler by method and path, parses URL params and puts params to context
   314  func (r *router) find(path string, paramsPointer *param.Params, unescape bool) (res nodeValue) {
   315  	var (
   316  		cn          = r.root // current node
   317  		search      = path   // current path
   318  		searchIndex = 0
   319  		buf         []byte
   320  		paramIndex  int
   321  	)
   322  
   323  	backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
   324  		previous := cn
   325  		cn = previous.parent
   326  		valid = cn != nil
   327  
   328  		// Next node type by priority
   329  		if previous.kind == akind {
   330  			nextNodeKind = skind
   331  		} else {
   332  			nextNodeKind = previous.kind + 1
   333  		}
   334  
   335  		if fromKind == skind {
   336  			// when backtracking is done from static kind block we did not change search so nothing to restore
   337  			return
   338  		}
   339  
   340  		// restore search to value it was before we move to current node we are backtracking from.
   341  		if previous.kind == skind {
   342  			searchIndex -= len(previous.prefix)
   343  		} else {
   344  			paramIndex--
   345  			// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
   346  			// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
   347  			searchIndex -= len((*paramsPointer)[paramIndex].Value)
   348  			(*paramsPointer) = (*paramsPointer)[:paramIndex]
   349  		}
   350  		search = path[searchIndex:]
   351  		return
   352  	}
   353  
   354  	// search order: static > param > any
   355  	for {
   356  		if cn.kind == skind {
   357  			if len(search) >= len(cn.prefix) && cn.prefix == search[:len(cn.prefix)] {
   358  				// Continue search
   359  				search = search[len(cn.prefix):]
   360  				searchIndex = searchIndex + len(cn.prefix)
   361  			} else {
   362  				// not equal
   363  				if (len(cn.prefix) == len(search)+1) &&
   364  					(cn.prefix[len(search)]) == '/' && cn.prefix[:len(search)] == search && (cn.handlers != nil || cn.anyChild != nil) {
   365  					res.tsr = true
   366  				}
   367  				// No matching prefix, let's backtrack to the first possible alternative node of the decision path
   368  				nk, ok := backtrackToNextNodeKind(skind)
   369  				if !ok {
   370  					return // No other possibilities on the decision path
   371  				} else if nk == pkind {
   372  					goto Param
   373  				} else {
   374  					// Not found (this should never be possible for static node we are looking currently)
   375  					break
   376  				}
   377  			}
   378  		}
   379  		if search == nilString && len(cn.handlers) != 0 {
   380  			res.handlers = cn.handlers
   381  			break
   382  		}
   383  
   384  		// Static node
   385  		if search != nilString {
   386  			// If it can execute that logic, there is handler registered on the current node and search is `/`.
   387  			if search == "/" && cn.handlers != nil {
   388  				res.tsr = true
   389  			}
   390  			if child := cn.findChild(search[0]); child != nil {
   391  				cn = child
   392  				continue
   393  			}
   394  		}
   395  
   396  		if search == nilString {
   397  			if cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) {
   398  				res.tsr = true
   399  			}
   400  		}
   401  
   402  	Param:
   403  		// Param node
   404  		if child := cn.paramChild; search != nilString && child != nil {
   405  			cn = child
   406  			i := strings.Index(search, slash)
   407  			if i == -1 {
   408  				i = len(search)
   409  			}
   410  			(*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)]
   411  			val := search[:i]
   412  			if unescape {
   413  				if v, err := url.QueryUnescape(search[:i]); err == nil {
   414  					val = v
   415  				}
   416  			}
   417  			(*paramsPointer)[paramIndex].Value = val
   418  			paramIndex++
   419  			search = search[i:]
   420  			searchIndex = searchIndex + i
   421  			if search == nilString {
   422  				if cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) {
   423  					res.tsr = true
   424  				}
   425  			}
   426  			continue
   427  		}
   428  	Any:
   429  		// Any node
   430  		if child := cn.anyChild; child != nil {
   431  			// If any node is found, use remaining path for paramValues
   432  			cn = child
   433  			(*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)]
   434  			index := len(cn.pnames) - 1
   435  			val := search
   436  			if unescape {
   437  				if v, err := url.QueryUnescape(search); err == nil {
   438  					val = v
   439  				}
   440  			}
   441  
   442  			(*paramsPointer)[index].Value = bytesconv.B2s(append(buf, val...))
   443  			// update indexes/search in case we need to backtrack when no handler match is found
   444  			paramIndex++
   445  			searchIndex += len(search)
   446  			search = nilString
   447  			res.handlers = cn.handlers
   448  			break
   449  		}
   450  
   451  		// Let's backtrack to the first possible alternative node of the decision path
   452  		nk, ok := backtrackToNextNodeKind(akind)
   453  		if !ok {
   454  			break // No other possibilities on the decision path
   455  		} else if nk == pkind {
   456  			goto Param
   457  		} else if nk == akind {
   458  			goto Any
   459  		} else {
   460  			// Not found
   461  			break
   462  		}
   463  	}
   464  
   465  	if cn != nil {
   466  		res.fullPath = cn.ppath
   467  		for i, name := range cn.pnames {
   468  			(*paramsPointer)[i].Key = name
   469  		}
   470  	}
   471  
   472  	return
   473  }
   474  
   475  func (n *node) findChild(l byte) *node {
   476  	for _, c := range n.children {
   477  		if c.label == l {
   478  			return c
   479  		}
   480  	}
   481  	return nil
   482  }
   483  
   484  func (n *node) findChildWithLabel(l byte) *node {
   485  	for _, c := range n.children {
   486  		if c.label == l {
   487  			return c
   488  		}
   489  	}
   490  	if l == paramLabel {
   491  		return n.paramChild
   492  	}
   493  	if l == anyLabel {
   494  		return n.anyChild
   495  	}
   496  	return nil
   497  }
   498  
   499  func newNode(t kind, pre string, p *node, child children, mh app.HandlersChain, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
   500  	return &node{
   501  		kind:       t,
   502  		label:      pre[0],
   503  		prefix:     pre,
   504  		parent:     p,
   505  		children:   child,
   506  		ppath:      ppath,
   507  		pnames:     pnames,
   508  		handlers:   mh,
   509  		paramChild: paramChildren,
   510  		anyChild:   anyChildren,
   511  		isLeaf:     child == nil && paramChildren == nil && anyChildren == nil,
   512  	}
   513  }
   514  
   515  // nodeValue holds return values of (*Node).getValue method
   516  type nodeValue struct {
   517  	handlers app.HandlersChain
   518  	tsr      bool
   519  	fullPath string
   520  }
   521  
   522  // Makes a case-insensitive lookup of the given path and tries to find a handler.
   523  // It returns the case-corrected path and a bool indicating whether the lookup
   524  // was successful.
   525  func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
   526  	ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
   527  	// Match paramKind.
   528  	if n.label == paramLabel {
   529  		end := 0
   530  		for end < len(path) && path[end] != '/' {
   531  			end++
   532  		}
   533  		ciPath = append(ciPath, path[:end]...)
   534  		if end < len(path) {
   535  			if len(n.children) > 0 {
   536  				path = path[end:]
   537  
   538  				goto loop
   539  			}
   540  
   541  			if fixTrailingSlash && len(path) == end+1 {
   542  				return ciPath, true
   543  			}
   544  			return
   545  		}
   546  
   547  		if n.handlers != nil {
   548  			return ciPath, true
   549  		}
   550  		if fixTrailingSlash && len(n.children) == 1 {
   551  			// No handle found. Check if a handle for this path with(without) a trailing slash exists
   552  			n = n.children[0]
   553  			if n.prefix == "/" && n.handlers != nil {
   554  				return append(ciPath, '/'), true
   555  			}
   556  		}
   557  		return
   558  	}
   559  
   560  	// Match allKind.
   561  	if n.label == anyLabel {
   562  		return append(ciPath, path...), true
   563  	}
   564  
   565  	// Match static kind.
   566  	if len(path) >= len(n.prefix) && strings.EqualFold(path[:len(n.prefix)], n.prefix) {
   567  		path = path[len(n.prefix):]
   568  		ciPath = append(ciPath, n.prefix...)
   569  
   570  		if len(path) == 0 {
   571  			if n.handlers != nil {
   572  				return ciPath, true
   573  			}
   574  			// No handle found.
   575  			// Try to fix the path by adding a trailing slash.
   576  			if fixTrailingSlash {
   577  				for i := 0; i < len(n.children); i++ {
   578  					if n.children[i].label == '/' {
   579  						n = n.children[i]
   580  						if (len(n.prefix) == 1 && n.handlers != nil) ||
   581  							(n.prefix == "*" && n.children[0].handlers != nil) {
   582  							return append(ciPath, '/'), true
   583  						}
   584  						return
   585  					}
   586  				}
   587  			}
   588  			return
   589  		}
   590  	} else if fixTrailingSlash {
   591  		// Nothing found.
   592  		// Try to fix the path by adding / removing a trailing slash.
   593  		if path == "/" {
   594  			return ciPath, true
   595  		}
   596  		if len(path)+1 == len(n.prefix) && n.prefix[len(path)] == '/' &&
   597  			strings.EqualFold(path, n.prefix[:len(path)]) &&
   598  			n.handlers != nil {
   599  			return append(ciPath, n.prefix...), true
   600  		}
   601  	}
   602  
   603  loop:
   604  	// First match static kind.
   605  	for _, node := range n.children {
   606  		if unicode.ToLower(rune(path[0])) == unicode.ToLower(rune(node.label)) {
   607  			out, found := node.findCaseInsensitivePath(path, fixTrailingSlash)
   608  			if found {
   609  				return append(ciPath, out...), true
   610  			}
   611  		}
   612  	}
   613  
   614  	if n.paramChild != nil {
   615  		out, found := n.paramChild.findCaseInsensitivePath(path, fixTrailingSlash)
   616  		if found {
   617  			return append(ciPath, out...), true
   618  		}
   619  	}
   620  
   621  	if n.anyChild != nil {
   622  		out, found := n.anyChild.findCaseInsensitivePath(path, fixTrailingSlash)
   623  		if found {
   624  			return append(ciPath, out...), true
   625  		}
   626  	}
   627  
   628  	// Nothing found. We can recommend to redirect to the same URL
   629  	// without a trailing slash if a leaf exists for that path
   630  	found = fixTrailingSlash && path == "/" && n.handlers != nil
   631  	return
   632  }