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  }