github.com/erda-project/erda-infra@v1.0.9/providers/httpserver/router.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  	"fmt"
    19  	"net/http"
    20  	"net/url"
    21  	"path"
    22  	"path/filepath"
    23  	"reflect"
    24  	"sort"
    25  	"strings"
    26  	"sync"
    27  
    28  	"github.com/labstack/echo"
    29  	"github.com/recallsong/go-utils/net/httpx/filesystem"
    30  
    31  	"github.com/erda-project/erda-infra/providers/httpserver/server"
    32  )
    33  
    34  type (
    35  	// Router .
    36  	Router interface {
    37  		GET(path string, handler interface{}, options ...interface{})
    38  		POST(path string, handler interface{}, options ...interface{})
    39  		DELETE(path string, handler interface{}, options ...interface{})
    40  		PUT(path string, handler interface{}, options ...interface{})
    41  		PATCH(path string, handler interface{}, options ...interface{})
    42  		HEAD(path string, handler interface{}, options ...interface{})
    43  		CONNECT(path string, handler interface{}, options ...interface{})
    44  		OPTIONS(path string, handler interface{}, options ...interface{})
    45  		TRACE(path string, handler interface{}, options ...interface{})
    46  
    47  		Any(path string, handler interface{}, options ...interface{})
    48  		Static(prefix, root string, options ...interface{})
    49  		File(path, filepath string, options ...interface{})
    50  
    51  		Add(method, path string, handler interface{}, options ...interface{}) error
    52  	}
    53  	// RouterManager .
    54  	RouterManager interface {
    55  		NewRouter(opts ...interface{}) RouterTx
    56  		Reloadable() bool
    57  		Started() <-chan struct{}
    58  	}
    59  	// RouterTx .
    60  	RouterTx interface {
    61  		Router
    62  		Commit() error
    63  		Rollback()
    64  		Reloadable() bool
    65  	}
    66  )
    67  
    68  type (
    69  	routeKey struct {
    70  		method string
    71  		path   string
    72  	}
    73  	route struct {
    74  		method string
    75  		path   string
    76  		group  string
    77  		hide   bool
    78  		desc   string
    79  
    80  		handler server.HandlerFunc
    81  	}
    82  	router struct {
    83  		lock         *sync.Mutex
    84  		done         bool
    85  		err          error
    86  		updateRoutes func(map[routeKey]*route)
    87  		reportError  func(err error)
    88  		tx           server.RouterTx
    89  		pathFormater *pathFormater
    90  		routes       map[routeKey]*route
    91  		group        string
    92  		interceptors []server.MiddlewareFunc
    93  	}
    94  )
    95  
    96  func (r *router) Add(method, path string, handler interface{}, options ...interface{}) error {
    97  	pathFormater := r.getPathFormater(options)
    98  	var pathParser server.MiddlewareFunc
    99  	if pathFormater.parser != nil {
   100  		pathParser = pathFormater.parser(path)
   101  	}
   102  	path = pathFormater.format(path)
   103  	method = strings.ToUpper(method)
   104  
   105  	key := routeKey{method: method, path: removeParamName(path)}
   106  	if rt, ok := r.routes[key]; ok {
   107  		if rt.group != r.group {
   108  			r.err = fmt.Errorf("httpserver routes [%s %s] conflict between groups (%s, %s)",
   109  				rt.method, key.path, rt.group, r.group)
   110  		} else {
   111  			r.err = fmt.Errorf("httpserver routes [%s %s] conflict in group %s",
   112  				rt.method, key.path, rt.group)
   113  		}
   114  		if r.lock == nil {
   115  			r.reportError(r.err)
   116  		}
   117  		return r.err
   118  	}
   119  	route := &route{
   120  		method: method,
   121  		path:   path,
   122  		group:  r.group,
   123  	}
   124  	for _, opt := range options {
   125  		processRouteOptions(route, opt)
   126  	}
   127  	r.routes[key] = route
   128  
   129  	if handler != nil {
   130  		interceptors := getInterceptors(options)
   131  		route.handler = r.add(method, path, handler, interceptors, pathParser)
   132  	}
   133  	return nil
   134  }
   135  
   136  func removeParamName(path string) string {
   137  	sb := &strings.Builder{}
   138  	chars := []rune(path)
   139  	for i, n := 0, len(chars); i < n; i++ {
   140  		c := chars[i]
   141  		if c != ':' {
   142  			sb.WriteRune(c)
   143  			continue
   144  		}
   145  		sb.WriteRune('*')
   146  		i++
   147  		for ; i < n && chars[i] != '/'; i++ {
   148  		}
   149  		if i < n {
   150  			sb.WriteRune(chars[i])
   151  		}
   152  	}
   153  	return sb.String()
   154  }
   155  
   156  type routeOption func(r *route)
   157  
   158  func processRouteOptions(r *route, opt interface{}) {
   159  	if fn, ok := opt.(routeOption); ok {
   160  		fn(r)
   161  	}
   162  }
   163  
   164  // WithDescription for Route, description for this route
   165  func WithDescription(desc string) interface{} {
   166  	return routeOption(func(r *route) {
   167  		r.desc = desc
   168  	})
   169  }
   170  
   171  // WithHide for Route, not print this route
   172  func WithHide(hide bool) interface{} {
   173  	return routeOption(func(r *route) {
   174  		r.hide = hide
   175  	})
   176  }
   177  
   178  // WithInterceptor for Router
   179  func WithInterceptor(fn func(handler func(ctx Context) error) func(ctx Context) error) interface{} {
   180  	return Interceptor(fn)
   181  }
   182  
   183  func (r *router) GET(path string, handler interface{}, options ...interface{}) {
   184  	r.Add(http.MethodGet, path, handler, options...)
   185  }
   186  
   187  func (r *router) POST(path string, handler interface{}, options ...interface{}) {
   188  	r.Add(http.MethodPost, path, handler, options...)
   189  }
   190  
   191  func (r *router) DELETE(path string, handler interface{}, options ...interface{}) {
   192  	r.Add(http.MethodDelete, path, handler, options...)
   193  }
   194  
   195  func (r *router) PUT(path string, handler interface{}, options ...interface{}) {
   196  	r.Add(http.MethodPut, path, handler, options...)
   197  }
   198  
   199  func (r *router) PATCH(path string, handler interface{}, options ...interface{}) {
   200  	r.Add(http.MethodPatch, path, handler, options...)
   201  }
   202  
   203  func (r *router) HEAD(path string, handler interface{}, options ...interface{}) {
   204  	r.Add(http.MethodHead, path, handler, options...)
   205  }
   206  
   207  func (r *router) CONNECT(path string, handler interface{}, options ...interface{}) {
   208  	r.Add(http.MethodConnect, path, handler, options...)
   209  }
   210  
   211  func (r *router) OPTIONS(path string, handler interface{}, options ...interface{}) {
   212  	r.Add(http.MethodOptions, path, handler, options...)
   213  }
   214  
   215  func (r *router) TRACE(path string, handler interface{}, options ...interface{}) {
   216  	r.Add(http.MethodTrace, path, handler, options...)
   217  }
   218  
   219  var allMethods = []string{
   220  	http.MethodConnect,
   221  	http.MethodDelete,
   222  	http.MethodGet,
   223  	http.MethodHead,
   224  	http.MethodOptions,
   225  	http.MethodPatch,
   226  	http.MethodPost,
   227  	http.MethodPut,
   228  	http.MethodTrace,
   229  }
   230  
   231  func init() { sort.Strings(allMethods) }
   232  
   233  func (r *router) Any(path string, handler interface{}, options ...interface{}) {
   234  	for _, method := range allMethods {
   235  		r.Add(method, path, handler, options...)
   236  	}
   237  }
   238  
   239  // WithFileSystem for Static And File
   240  func WithFileSystem(fs http.FileSystem) interface{} {
   241  	return fs
   242  }
   243  
   244  type filesystemPath string
   245  
   246  // WithFileSystemPath for Static And File
   247  func WithFileSystemPath(root string) interface{} {
   248  	return filesystemPath(root)
   249  }
   250  
   251  func (r *router) Static(prefix, root string, options ...interface{}) {
   252  	var fs http.FileSystem
   253  	for _, opt := range options {
   254  		if files, ok := opt.(http.FileSystem); ok {
   255  			fs = files
   256  		} else if path, ok := opt.(filesystemPath); ok {
   257  			root = filepath.Join(string(path), root)
   258  		}
   259  	}
   260  	if root == "" {
   261  		root = "."
   262  	}
   263  	if fs == nil {
   264  		r.addPrefix(prefix, root, func(c echo.Context) error {
   265  			p, err := url.PathUnescape(c.Param("*"))
   266  			if err != nil {
   267  				return err
   268  			}
   269  			name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
   270  			return c.File(name)
   271  		}, options...)
   272  	} else {
   273  		fs := filesystem.New(fs).SetRoot(root).SetRoute(prefix)
   274  		handler := fs.Handler
   275  		r.addPrefix(prefix, root, func(c server.Context) error {
   276  			handler.ServeHTTP(c.Response(), c.Request())
   277  			return nil
   278  		}, options...)
   279  	}
   280  }
   281  
   282  func (r *router) addPrefix(prefix, root string, h func(c echo.Context) error, options ...interface{}) {
   283  	r.GET(prefix, h, options...)
   284  	if prefix == "/" {
   285  		r.GET(prefix+"*", h, options...)
   286  	} else {
   287  		r.GET(prefix+"/*", h, options...)
   288  	}
   289  }
   290  
   291  func (r *router) File(path, file string, options ...interface{}) {
   292  	var fs http.FileSystem
   293  	for _, opt := range options {
   294  		if files, ok := opt.(http.FileSystem); ok {
   295  			fs = files
   296  		} else if path, ok := opt.(filesystemPath); ok {
   297  			file = filepath.Join(string(path), file)
   298  		}
   299  	}
   300  	if fs == nil {
   301  		r.GET(path, func(c echo.Context) error {
   302  			return c.File(file)
   303  		}, options...)
   304  	} else {
   305  		fs := filesystem.New(fs).SetRoot(file).SetRoute(path)
   306  		handler := fs.Handler
   307  		r.GET(path, func(c server.Context) error {
   308  			handler.ServeHTTP(c.Response(), c.Request())
   309  			return nil
   310  		}, options...)
   311  	}
   312  }
   313  
   314  // Commit .
   315  func (r *router) Commit() error {
   316  	if r.lock != nil {
   317  		if r.done {
   318  			return fmt.Errorf("routes commited")
   319  		}
   320  		r.done = true
   321  		if r.err != nil {
   322  			r.reportError(r.err)
   323  			r.lock.Unlock()
   324  			return r.err
   325  		}
   326  		r.tx.Commit()
   327  		r.updateRoutes(r.routes)
   328  		r.lock.Unlock()
   329  	}
   330  	return r.err
   331  }
   332  
   333  // Rollback .
   334  func (r *router) Rollback() {
   335  	if r.lock != nil {
   336  		r.lock.Unlock()
   337  	}
   338  }
   339  
   340  // Reloadable .
   341  func (r *router) Reloadable() bool { return r.lock != nil }
   342  
   343  type routesSorter []*route
   344  
   345  func (s routesSorter) Len() int      { return len(s) }
   346  func (s routesSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   347  func (s routesSorter) Less(i, j int) bool {
   348  	if s[i].group == s[j].group {
   349  		if s[i].path == s[j].path {
   350  			return s[i].method < s[j].method
   351  		}
   352  		return s[i].path < s[j].path
   353  	}
   354  	return s[i].group < s[j].group
   355  }
   356  
   357  func listRoutes(routeMap map[routeKey]*route) []*route {
   358  	routes := make([]*route, 0, len(routeMap))
   359  	for _, route := range routeMap {
   360  		routes = append(routes, route)
   361  	}
   362  	sort.Sort(routesSorter(routes))
   363  	return routes
   364  }
   365  
   366  func (p *provider) printRoutes(routes map[routeKey]*route) {
   367  	list := listRoutes(routes)
   368  	var group, path string
   369  	var methods []string
   370  	printRoute := func(group, path string, methods []string) {
   371  		sort.Strings(methods)
   372  		if reflect.DeepEqual(methods, allMethods) {
   373  			p.Log.Infof("%s --> [%s] %-7s %s", p.Cfg.Addr, group, "*", path)
   374  		} else {
   375  			for _, method := range methods {
   376  				p.Log.Infof("%s --> [%s] %-7s %s", p.Cfg.Addr, group, method, path)
   377  			}
   378  		}
   379  	}
   380  	for _, route := range list {
   381  		if route.hide {
   382  			continue
   383  		}
   384  		if methods == nil {
   385  			group, path = route.group, route.path
   386  			methods = []string{route.method}
   387  			continue
   388  		} else if path == route.path && group == route.group {
   389  			methods = append(methods, route.method)
   390  			continue
   391  		}
   392  		printRoute(group, path, methods)
   393  		group, path = route.group, route.path
   394  		methods = []string{route.method}
   395  	}
   396  	if len(methods) > 0 {
   397  		printRoute(group, path, methods)
   398  	}
   399  }
   400  
   401  type routerManager struct {
   402  	group string
   403  	reset bool
   404  	opts  []interface{}
   405  	p     *provider
   406  }
   407  
   408  func (rm *routerManager) NewRouter(opts ...interface{}) RouterTx {
   409  	args := make([]interface{}, len(rm.opts)+len(opts))
   410  	copy(args, rm.opts)
   411  	copy(args[len(rm.opts):], opts)
   412  	return rm.p.newRouterTx(rm.reset, rm.group, args...)
   413  }
   414  
   415  func (rm *routerManager) Reloadable() bool { return rm.p.Cfg.Reloadable }
   416  
   417  func (rm *routerManager) Started() <-chan struct{} {
   418  	return rm.p.startedChan
   419  }
   420  
   421  type autoCommitRouter struct {
   422  	tx     RouterManager
   423  	routes []route
   424  }
   425  
   426  var _ Router = (*autoCommitRouter)(nil)
   427  
   428  func (r *autoCommitRouter) add(method, path string, handler interface{}, options ...interface{}) {
   429  	tx := r.tx.NewRouter()
   430  	tx.Add(method, path, handler, options...)
   431  	r.commit(tx)
   432  }
   433  func (r *autoCommitRouter) commit(tx RouterTx) {
   434  	err := tx.Commit()
   435  	if err != nil {
   436  		panic(err)
   437  	}
   438  }
   439  func (r *autoCommitRouter) GET(path string, handler interface{}, options ...interface{}) {
   440  	r.add(http.MethodGet, path, handler, options...)
   441  }
   442  func (r *autoCommitRouter) POST(path string, handler interface{}, options ...interface{}) {
   443  	r.add(http.MethodPost, path, handler, options...)
   444  }
   445  func (r *autoCommitRouter) DELETE(path string, handler interface{}, options ...interface{}) {
   446  	r.add(http.MethodDelete, path, handler, options...)
   447  }
   448  func (r *autoCommitRouter) PUT(path string, handler interface{}, options ...interface{}) {
   449  	r.add(http.MethodPut, path, handler, options...)
   450  }
   451  func (r *autoCommitRouter) PATCH(path string, handler interface{}, options ...interface{}) {
   452  	r.add(http.MethodPatch, path, handler, options...)
   453  }
   454  func (r *autoCommitRouter) HEAD(path string, handler interface{}, options ...interface{}) {
   455  	r.add(http.MethodHead, path, handler, options...)
   456  }
   457  func (r *autoCommitRouter) CONNECT(path string, handler interface{}, options ...interface{}) {
   458  	r.add(http.MethodConnect, path, handler, options...)
   459  }
   460  func (r *autoCommitRouter) OPTIONS(path string, handler interface{}, options ...interface{}) {
   461  	r.add(http.MethodOptions, path, handler, options...)
   462  }
   463  func (r *autoCommitRouter) TRACE(path string, handler interface{}, options ...interface{}) {
   464  	r.add(http.MethodTrace, path, handler, options...)
   465  }
   466  func (r *autoCommitRouter) Any(path string, handler interface{}, options ...interface{}) {
   467  	tx := r.tx.NewRouter()
   468  	tx.Any(path, handler, options...)
   469  	r.commit(tx)
   470  }
   471  func (r *autoCommitRouter) Static(prefix, root string, options ...interface{}) {
   472  	tx := r.tx.NewRouter()
   473  	tx.Static(prefix, root, options...)
   474  	r.commit(tx)
   475  }
   476  func (r *autoCommitRouter) File(path, filepath string, options ...interface{}) {
   477  	tx := r.tx.NewRouter()
   478  	tx.File(path, filepath, options...)
   479  	r.commit(tx)
   480  }
   481  func (r *autoCommitRouter) Add(method, path string, handler interface{}, options ...interface{}) error {
   482  	tx := r.tx.NewRouter()
   483  	tx.GET(path, handler, options...)
   484  	return tx.Commit()
   485  }