github.com/erda-project/erda-infra@v1.0.9/pkg/transport/http/runtime/pattern.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Reference: https://github.com/grpc-ecosystem/grpc-gateway/blob/v2.3.0/runtime/pattern.go 16 17 package runtime 18 19 import ( 20 "errors" 21 "fmt" 22 "strings" 23 24 "github.com/erda-project/erda-infra/pkg/transport/http/utilities" 25 "google.golang.org/grpc/grpclog" 26 ) 27 28 var ( 29 // ErrNotMatch indicates that the given HTTP request path does not match to the pattern. 30 ErrNotMatch = errors.New("not match to the path pattern") 31 // ErrInvalidPattern indicates that the given definition of Pattern is not valid. 32 ErrInvalidPattern = errors.New("invalid pattern") 33 ) 34 35 type op struct { 36 code utilities.OpCode 37 operand int 38 } 39 40 // Pattern is a template pattern of http request paths defined in 41 // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto 42 type Pattern struct { 43 // ops is a list of operations 44 ops []op 45 // pool is a constant pool indexed by the operands or vars. 46 pool []string 47 // vars is a list of variables names to be bound by this pattern 48 vars []string 49 // stacksize is the max depth of the stack 50 stacksize int 51 // tailLen is the length of the fixed-size segments after a deep wildcard 52 tailLen int 53 // verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part. 54 verb string 55 } 56 57 // NewPattern returns a new Pattern from the given definition values. 58 // "ops" is a sequence of op codes. "pool" is a constant pool. 59 // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part. 60 // "version" must be 1 for now. 61 // It returns an error if the given definition is invalid. 62 func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) { 63 if version != 1 { 64 grpclog.Infof("unsupported version: %d", version) 65 return Pattern{}, ErrInvalidPattern 66 } 67 68 l := len(ops) 69 if l%2 != 0 { 70 grpclog.Infof("odd number of ops codes: %d", l) 71 return Pattern{}, ErrInvalidPattern 72 } 73 74 var ( 75 typedOps []op 76 stack, maxstack int 77 tailLen int 78 pushMSeen bool 79 vars []string 80 ) 81 for i := 0; i < l; i += 2 { 82 op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]} 83 switch op.code { 84 case utilities.OpNop: 85 continue 86 case utilities.OpPush: 87 if pushMSeen { 88 tailLen++ 89 } 90 stack++ 91 case utilities.OpPushM: 92 if pushMSeen { 93 grpclog.Infof("pushM appears twice") 94 return Pattern{}, ErrInvalidPattern 95 } 96 pushMSeen = true 97 stack++ 98 case utilities.OpLitPush: 99 if op.operand < 0 || len(pool) <= op.operand { 100 grpclog.Infof("negative literal index: %d", op.operand) 101 return Pattern{}, ErrInvalidPattern 102 } 103 if pushMSeen { 104 tailLen++ 105 } 106 stack++ 107 case utilities.OpConcatN: 108 if op.operand <= 0 { 109 grpclog.Infof("negative concat size: %d", op.operand) 110 return Pattern{}, ErrInvalidPattern 111 } 112 stack -= op.operand 113 if stack < 0 { 114 grpclog.Info("stack underflow") 115 return Pattern{}, ErrInvalidPattern 116 } 117 stack++ 118 case utilities.OpCapture: 119 if op.operand < 0 || len(pool) <= op.operand { 120 grpclog.Infof("variable name index out of bound: %d", op.operand) 121 return Pattern{}, ErrInvalidPattern 122 } 123 v := pool[op.operand] 124 op.operand = len(vars) 125 vars = append(vars, v) 126 stack-- 127 if stack < 0 { 128 grpclog.Infof("stack underflow") 129 return Pattern{}, ErrInvalidPattern 130 } 131 default: 132 grpclog.Infof("invalid opcode: %d", op.code) 133 return Pattern{}, ErrInvalidPattern 134 } 135 136 if maxstack < stack { 137 maxstack = stack 138 } 139 typedOps = append(typedOps, op) 140 } 141 return Pattern{ 142 ops: typedOps, 143 pool: pool, 144 vars: vars, 145 stacksize: maxstack, 146 tailLen: tailLen, 147 verb: verb, 148 }, nil 149 } 150 151 // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization. 152 func MustPattern(p Pattern, err error) Pattern { 153 if err != nil { 154 grpclog.Fatalf("Pattern initialization failed: %v", err) 155 } 156 return p 157 } 158 159 // Match examines components if it matches to the Pattern. 160 // If it matches, the function returns a mapping from field paths to their captured values. 161 // If otherwise, the function returns an error. 162 func (p Pattern) Match(components []string, verb string) (map[string]string, error) { 163 if p.verb != verb { 164 if p.verb != "" { 165 return nil, ErrNotMatch 166 } 167 if len(components) == 0 { 168 components = []string{":" + verb} 169 } else { 170 components = append([]string{}, components...) 171 components[len(components)-1] += ":" + verb 172 } 173 } 174 175 var pos int 176 stack := make([]string, 0, p.stacksize) 177 captured := make([]string, len(p.vars)) 178 l := len(components) 179 for _, op := range p.ops { 180 switch op.code { 181 case utilities.OpNop: 182 continue 183 case utilities.OpPush, utilities.OpLitPush: 184 if pos >= l { 185 return nil, ErrNotMatch 186 } 187 c := components[pos] 188 if op.code == utilities.OpLitPush { 189 if lit := p.pool[op.operand]; c != lit { 190 return nil, ErrNotMatch 191 } 192 } 193 stack = append(stack, c) 194 pos++ 195 case utilities.OpPushM: 196 end := len(components) 197 if end < pos+p.tailLen { 198 return nil, ErrNotMatch 199 } 200 end -= p.tailLen 201 stack = append(stack, strings.Join(components[pos:end], "/")) 202 pos = end 203 case utilities.OpConcatN: 204 n := op.operand 205 l := len(stack) - n 206 stack = append(stack[:l], strings.Join(stack[l:], "/")) 207 case utilities.OpCapture: 208 n := len(stack) - 1 209 captured[op.operand] = stack[n] 210 stack = stack[:n] 211 } 212 } 213 if pos < l { 214 return nil, ErrNotMatch 215 } 216 bindings := make(map[string]string) 217 for i, val := range captured { 218 bindings[p.vars[i]] = val 219 } 220 return bindings, nil 221 } 222 223 // Verb returns the verb part of the Pattern. 224 func (p Pattern) Verb() string { return p.verb } 225 226 func (p Pattern) String() string { 227 var stack []string 228 for _, op := range p.ops { 229 switch op.code { 230 case utilities.OpNop: 231 continue 232 case utilities.OpPush: 233 stack = append(stack, "*") 234 case utilities.OpLitPush: 235 stack = append(stack, p.pool[op.operand]) 236 case utilities.OpPushM: 237 stack = append(stack, "**") 238 case utilities.OpConcatN: 239 n := op.operand 240 l := len(stack) - n 241 stack = append(stack[:l], strings.Join(stack[l:], "/")) 242 case utilities.OpCapture: 243 n := len(stack) - 1 244 stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n]) 245 } 246 } 247 segs := strings.Join(stack, "/") 248 if p.verb != "" { 249 return fmt.Sprintf("/%s:%s", segs, p.verb) 250 } 251 return "/" + segs 252 }