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 }