trpc.group/trpc-go/trpc-go@v1.0.3/internal/httprule/match.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package httprule
    15  
    16  import (
    17  	"errors"
    18  	"strings"
    19  	"sync"
    20  
    21  	"trpc.group/trpc-go/trpc-go/internal/stack"
    22  )
    23  
    24  var (
    25  	errNotMatch       = errors.New("not match to the path template")
    26  	errVerbMismatched = errors.New("verb mismatched")
    27  )
    28  
    29  // matcher is used to match variable values from template.
    30  type matcher struct {
    31  	components []string             // urlPath: "/foo/bar/baz" => []string{"foo","bar","baz"}
    32  	pos        int                  // pos is the current match position, initialized before every match.
    33  	captured   map[string]string    // values that has already been captured.
    34  	st         *stack.Stack[string] // st is a stack to aid the matching process.
    35  }
    36  
    37  // matcher pool.
    38  var matcherPool = sync.Pool{
    39  	New: func() interface{} {
    40  		return &matcher{}
    41  	},
    42  }
    43  
    44  // putBackMatch puts the matcher back to the pool.
    45  func putBackMatcher(m *matcher) {
    46  	m.components = nil
    47  	m.pos = 0
    48  	m.captured = nil
    49  	m.st.Reset()
    50  	stackPool.Put(m.st)
    51  	m.st = nil
    52  	matcherPool.Put(m)
    53  }
    54  
    55  // stack pool.
    56  var stackPool = sync.Pool{
    57  	New: func() interface{} {
    58  		return stack.New[string]()
    59  	},
    60  }
    61  
    62  // handle implements segment.
    63  func (wildcard) handle(m *matcher) error {
    64  	// prevent out-of-bounds error.
    65  	if m.pos >= len(m.components) {
    66  		return errNotMatch
    67  	}
    68  
    69  	// "*" could match any component, push it into the stack directly.
    70  	m.st.Push(m.components[m.pos])
    71  	m.pos++
    72  
    73  	return nil
    74  }
    75  
    76  // handle implements segment.
    77  func (deepWildcard) handle(m *matcher) error {
    78  	// prevent out-of-bounds error.
    79  	if m.pos > len(m.components) {
    80  		return errNotMatch
    81  	}
    82  	// m.pos = len(m.components) is allowed, because "**" could match any number of components.
    83  	if m.pos == len(m.components) {
    84  		m.st.Push("")
    85  		return nil
    86  	}
    87  
    88  	var sb strings.Builder
    89  	for ; m.pos < len(m.components); m.pos++ {
    90  		sb.WriteRune('/')
    91  		sb.WriteString(m.components[m.pos])
    92  	}
    93  	m.st.Push(sb.String()[1:])
    94  
    95  	return nil
    96  }
    97  
    98  // handle implements segment.
    99  func (l literal) handle(m *matcher) error {
   100  	// prevent out-of-bounds error.
   101  	if m.pos >= len(m.components) {
   102  		return errNotMatch
   103  	}
   104  
   105  	// literal value should equal to the current component.
   106  	if m.components[m.pos] != l.String() {
   107  		return errNotMatch
   108  	}
   109  
   110  	// matched, push it into the stack.
   111  	m.st.Push(m.components[m.pos])
   112  	m.pos++
   113  
   114  	return nil
   115  }
   116  
   117  // handle implements segment.
   118  func (v variable) handle(m *matcher) error {
   119  	// match segments recursively.
   120  	for _, segment := range v.segments {
   121  		if err := segment.handle(m); err != nil {
   122  			return err
   123  		}
   124  	}
   125  
   126  	// concatenate the popped components.
   127  	// the final result is the captured value of v.fieldPath.
   128  	concat := make([]string, len(v.segments))
   129  	for i := len(v.segments) - 1; i >= 0; i-- {
   130  		s, ok := m.st.Pop()
   131  		if !ok {
   132  			return errNotMatch
   133  		}
   134  		concat[i] = s
   135  	}
   136  	m.captured[strings.Join(v.fp, ".")] = strings.Join(concat, "/")
   137  
   138  	return nil
   139  }
   140  
   141  // Match matches the value of variables according to the given HTTP URL path.
   142  func (tpl *PathTemplate) Match(urlPath string) (map[string]string, error) {
   143  	// must start with '/'
   144  	if !strings.HasPrefix(urlPath, "/") {
   145  		return nil, errNotMatch
   146  	}
   147  
   148  	urlPath = urlPath[1:]
   149  	components := strings.Split(urlPath, "/")
   150  
   151  	// verb match.
   152  	if tpl.verb != "" {
   153  		if !strings.HasSuffix(components[len(components)-1], ":"+tpl.verb) {
   154  			return nil, errVerbMismatched
   155  		}
   156  		idx := len(components[len(components)-1]) - len(tpl.verb) - 1
   157  		if idx <= 0 {
   158  			return nil, errVerbMismatched
   159  		}
   160  		components[len(components)-1] = components[len(components)-1][:idx]
   161  	}
   162  
   163  	// use sync.Pool to reuse memory, since the initialization of matcher/match
   164  	// is of high frequency.
   165  	m := matcherPool.Get().(*matcher)
   166  	defer putBackMatcher(m)
   167  	m.components = components
   168  	m.captured = make(map[string]string)
   169  	// use sync.Pool to reuse memory of stack.Stack.
   170  	m.st = stackPool.Get().(*stack.Stack[string])
   171  
   172  	// segments match.
   173  	for _, segment := range tpl.segments {
   174  		if err := segment.handle(m); err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  
   179  	// check whether pos reaches the end.
   180  	if m.pos != len(m.components) {
   181  		return nil, errNotMatch
   182  	}
   183  
   184  	return m.captured, nil
   185  }