github.com/erda-project/erda-infra@v1.0.9/providers/httpserver/path.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 package httpserver 16 17 import ( 18 libcontext "context" 19 "net/http" 20 "net/url" 21 "strings" 22 23 "github.com/erda-project/erda-infra/pkg/transport/http/runtime" 24 "github.com/erda-project/erda-infra/providers/httpserver/server" 25 ) 26 27 // PathFormat . 28 type PathFormat int32 29 30 // PathFormat values 31 const ( 32 PathFormatEcho = 0 33 PathFormatGoogleAPIs = 1 34 ) 35 36 type contextKey int 37 38 const ( 39 varsKey contextKey = iota 40 ) 41 42 // WithPathFormat . 43 func WithPathFormat(format PathFormat) interface{} { 44 formater := &pathFormater{typ: format} 45 switch format { 46 case PathFormatGoogleAPIs: 47 formater.format = buildGoogleAPIsPath 48 formater.parser = googleAPIsPathParamsInterceptor 49 default: 50 formater.format = buildEchoPath 51 } 52 return formater 53 } 54 55 func (r *router) getPathFormater(options []interface{}) *pathFormater { 56 pformater := r.pathFormater 57 for _, arg := range options { 58 if f, ok := arg.(*pathFormater); ok { 59 pformater = f 60 } 61 } 62 if pformater == nil { 63 pformater = newPathFormater() 64 } 65 return pformater 66 } 67 68 type pathFormater struct { 69 typ PathFormat 70 format func(string) string 71 parser func(path string) func(server.HandlerFunc) server.HandlerFunc 72 } 73 74 func newPathFormater() *pathFormater { 75 return &pathFormater{ 76 typ: PathFormatEcho, 77 format: buildEchoPath, 78 } 79 } 80 81 func buildEchoPath(p string) string { return p } 82 83 // convert googleapis path to echo path 84 func buildGoogleAPIsPath(path string) string { 85 // skip query string 86 idx := strings.Index(path, "?") 87 if idx >= 0 { 88 path = path[0:idx] 89 } 90 91 sb := &strings.Builder{} 92 chars := []rune(path) 93 start, i, l := 0, 0, len(chars) 94 for ; i < l; i++ { 95 c := chars[i] 96 switch c { 97 case '{': 98 sb.WriteString(string(chars[start:i])) 99 start = i 100 var hasEq bool 101 var name string 102 begin := i 103 i++ // skip '{' 104 loop: 105 for ; i < l; i++ { 106 c = chars[i] 107 switch c { 108 case '}': 109 begin++ // skip '{' or '=' 110 if len(chars[begin:i]) <= 0 || len(chars[begin:i]) == 1 && chars[begin] == '*' { 111 sb.WriteString("*") 112 } else if hasEq { 113 pattern := string(chars[begin:i]) 114 if !strings.HasPrefix(pattern, "/") { 115 idx := strings.Index(pattern, "/") 116 if idx >= 0 { 117 pattern = pattern[idx:] 118 } else { 119 pattern = "" 120 } 121 } 122 sb.WriteString(":" + name + strings.ReplaceAll(pattern, ":", "%3A")) // replace ":" to %3A 123 } else { 124 sb.WriteString(":" + name + url.PathEscape(string(chars[begin:i]))) 125 } 126 start = i + 1 // skip '}' 127 break loop 128 case '=': 129 name = url.PathEscape(string(chars[begin+1 : i])) 130 hasEq = true 131 begin = i 132 } 133 } 134 } 135 } 136 if start < l { 137 sb.WriteString(strings.ReplaceAll(string(chars[start:]), ":", "%3A")) // replace ":" to %3A 138 } 139 return sb.String() 140 } 141 142 func googleAPIsPathParamsInterceptor(path string) func(server.HandlerFunc) server.HandlerFunc { 143 raw := func(handler server.HandlerFunc) server.HandlerFunc { 144 return handler 145 } 146 matcher, err := runtime.Compile(path) 147 if err != nil { 148 // panic(fmt.Errorf("path %q error: %s", path, err)) 149 return func(server.HandlerFunc) server.HandlerFunc { 150 return func(ctx server.Context) error { 151 return server.NotFoundHandler(ctx) 152 } 153 } 154 } 155 if matcher.IsStatic() { 156 return raw 157 } 158 return func(handler server.HandlerFunc) server.HandlerFunc { 159 return func(ctx server.Context) error { 160 path := ctx.Request().URL.Path 161 vars, err := matcher.Match(path) 162 if err != nil { 163 return server.NotFoundHandler(ctx) 164 } 165 c := ctx.(*context) 166 c.vars = vars 167 ctx = c 168 ctx.SetRequest(ctx.Request().WithContext(makeCtxWithVars(ctx.Request().Context(), vars))) 169 return handler(ctx) 170 } 171 } 172 } 173 174 func makeCtxWithVars(ctx libcontext.Context, vars map[string]string) libcontext.Context { 175 return libcontext.WithValue(ctx, varsKey, vars) 176 } 177 178 // Vars returns the route variables for the current request, if any. 179 func Vars(r *http.Request) map[string]string { 180 if rv := r.Context().Value(varsKey); rv != nil { 181 return rv.(map[string]string) 182 } 183 return nil 184 } 185 186 // Var return the specified variable value and exist from the current request, if any. 187 func Var(r *http.Request, key string) (string, bool) { 188 vars := Vars(r) 189 if vars == nil { 190 return "", false 191 } 192 val, ok := vars[key] 193 return val, ok 194 }