github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/httprouter/router.go (about)

     1  // Copyright 2013 Julien Schmidt. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be found
     3  // in the LICENSE file.
     4  
     5  // Package httprouter is a trie based high performance HTTP request router.
     6  //
     7  // A trivial example is:
     8  //
     9  //  package main
    10  //
    11  //  import (
    12  //      "fmt"
    13  //      "github.com/julienschmidt/httprouter"
    14  //      "net/http"
    15  //      "log"
    16  //  )
    17  //
    18  //  func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    19  //      fmt.Fprint(w, "Welcome!\n")
    20  //  }
    21  //
    22  //  func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    23  //      fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
    24  //  }
    25  //
    26  //  func main() {
    27  //      router := httprouter.New()
    28  //      router.GET("/", Index)
    29  //      router.GET("/hello/:name", Hello)
    30  //
    31  //      log.Fatal(http.ListenAndServe(":8080", router))
    32  //  }
    33  //
    34  // The router matches incoming requests by the request method and the path.
    35  // If a handle is registered for this path and method, the router delegates the
    36  // request to that function.
    37  // For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to
    38  // register handles, for all other methods router.Handle can be used.
    39  //
    40  // The registered path, against which the router matches incoming requests, can
    41  // contain two types of parameters:
    42  //  Syntax    Type
    43  //  :name     named parameter
    44  //  *name     catch-all parameter
    45  //
    46  // Named parameters are dynamic path segments. They match anything until the
    47  // next '/' or the path end:
    48  //  Path: /blog/:category/:post
    49  //
    50  //  Requests:
    51  //   /blog/go/request-routers            match: category="go", post="request-routers"
    52  //   /blog/go/request-routers/           no match, but the router would redirect
    53  //   /blog/go/                           no match
    54  //   /blog/go/request-routers/comments   no match
    55  //
    56  // Catch-all parameters match anything until the path end, including the
    57  // directory index (the '/' before the catch-all). Since they match anything
    58  // until the end, catch-all parameters must always be the final path element.
    59  //  Path: /files/*filepath
    60  //
    61  //  Requests:
    62  //   /files/                             match: filepath="/"
    63  //   /files/LICENSE                      match: filepath="/LICENSE"
    64  //   /files/templates/article.html       match: filepath="/templates/article.html"
    65  //   /files                              no match, but the router would redirect
    66  //
    67  // The value of parameters is saved as a slice of the Param struct, consisting
    68  // each of a key and a value. The slice is passed to the Handle func as a third
    69  // parameter.
    70  // There are two ways to retrieve the value of a parameter:
    71  //  // by the name of the parameter
    72  //  user := ps.ByName("user") // defined by :user or *user
    73  //
    74  //  // by the index of the parameter. This way you can also get the name (key)
    75  //  thirdKey   := ps[2].Key   // the name of the 3rd parameter
    76  //  thirdValue := ps[2].Value // the value of the 3rd parameter
    77  package httprouter
    78  
    79  import (
    80  	"context"
    81  	"github.com/ucarion/urlpath"
    82  	"net/http"
    83  	"strings"
    84  	"sync"
    85  )
    86  
    87  // Handle is a function that can be registered to a route to handle HTTP
    88  // requests. Like http.HandlerFunc, but has a third parameter for the values of
    89  // wildcards (path variables).
    90  type Handle func(http.ResponseWriter, *http.Request, Params)
    91  
    92  // Param is a single URL parameter, consisting of a key and a value.
    93  type Param struct {
    94  	Key   string
    95  	Value string
    96  }
    97  
    98  // Params is a Param-slice, as returned by the router.
    99  // The slice is ordered, the first URL parameter is also the first slice value.
   100  // It is therefore safe to read values by the index.
   101  type Params []Param
   102  
   103  // ByName returns the value of the first Param which key matches the given name.
   104  // If no matching Param is found, an empty string is returned.
   105  func (ps Params) ByName(name string) string {
   106  	for _, p := range ps {
   107  		if p.Key == name {
   108  			return p.Value
   109  		}
   110  	}
   111  	return ""
   112  }
   113  
   114  type paramsKey struct{}
   115  
   116  // ParamsKey is the request context key under which URL params are stored.
   117  var ParamsKey = paramsKey{}
   118  
   119  // ParamsFromContext pulls the URL parameters from a request context,
   120  // or returns nil if none are present.
   121  func ParamsFromContext(ctx context.Context) Params {
   122  	p, _ := ctx.Value(ParamsKey).(Params)
   123  	return p
   124  }
   125  
   126  // MatchedRouteNameParam is the Param name under which the name of the matched
   127  // route is stored, if Router.SaveMatchedRoutePath is set.
   128  var MatchedRouteNameParam = "$matchedRouteName"
   129  
   130  // MatchedRouteName retrieves the name of the matched route.
   131  // Router.SaveMatchedRoutePath must have been enabled when the respective
   132  // handler was added, otherwise this function always returns an empty string.
   133  func (ps Params) MatchedRouteName() string {
   134  	return ps.ByName(MatchedRouteNameParam)
   135  }
   136  
   137  // Router is a http.Handler which can be used to dispatch requests to different
   138  // handler functions via configurable routes
   139  type Router struct {
   140  	paramsPool sync.Pool
   141  
   142  	// If enabled, adds the matched route path onto the http.Request context
   143  	// before invoking the handler.
   144  	// The matched route path is only added to handlers of routes that were
   145  	// registered when this option was enabled.
   146  	SaveMatchedRoutePath bool
   147  
   148  	// If enabled, the router checks if another method is allowed for the
   149  	// current route, if the current request can not be routed.
   150  	// If this is the case, the request is answered with 'Method Not Allowed'
   151  	// and HTTP status code 405.
   152  	// If no other Method is allowed, the request is delegated to the NotFound
   153  	// handler.
   154  	HandleMethodNotAllowed bool
   155  
   156  	// If enabled, the router automatically replies to OPTIONS requests.
   157  	// Custom OPTIONS handlers take priority over automatic replies.
   158  	HandleOPTIONS bool
   159  
   160  	// An optional http.Handler that is called on automatic OPTIONS requests.
   161  	// The handler is only called if HandleOPTIONS is true and no OPTIONS
   162  	// handler for the specific path was set.
   163  	// The "Allowed" header is set before calling the handler.
   164  	GlobalOPTIONS http.Handler
   165  
   166  	// Cached value of global (*) allowed methods
   167  	globalAllowed string
   168  
   169  	// Configurable http.Handler which is called when no matching route is
   170  	// found. If it is not set, http.NotFound is used.
   171  	NotFound http.Handler
   172  
   173  	// Configurable http.Handler which is called when a request
   174  	// cannot be routed and HandleMethodNotAllowed is true.
   175  	// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
   176  	// The "Allow" header with allowed request methods is set before the handler
   177  	// is called.
   178  	MethodNotAllowed http.Handler
   179  
   180  	// Function to handle panics recovered from http handlers.
   181  	// It should be used to generate a error page and return the http error code
   182  	// 500 (Internal Server Error).
   183  	// The handler can be used to keep your server from crashing because of
   184  	// unrecovered panics.
   185  	PanicHandler func(http.ResponseWriter, *http.Request, interface{})
   186  
   187  	registeredPaths map[string][]string
   188  	handlers        map[string]Handle
   189  	dynamicHandlers []map[*urlpath.Path]Handle
   190  }
   191  
   192  // Make sure the Router conforms with the http.Handler interface
   193  var _ http.Handler = New()
   194  
   195  var httpMethods = []string{http.MethodGet, http.MethodHead, http.MethodPost,
   196  	http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect,
   197  	http.MethodOptions, http.MethodTrace}
   198  
   199  // New returns a new initialized Router.
   200  // Path auto-correction, including trailing slashes, is enabled by default.
   201  func New() *Router {
   202  	r := &Router{
   203  		HandleMethodNotAllowed: true,
   204  		HandleOPTIONS:          true,
   205  		registeredPaths:        make(map[string][]string),
   206  		handlers:               make(map[string]Handle),
   207  		dynamicHandlers:        make([]map[*urlpath.Path]Handle, len(httpMethods)),
   208  	}
   209  	for i := range httpMethods {
   210  		r.dynamicHandlers[i] = make(map[*urlpath.Path]Handle)
   211  	}
   212  	r.paramsPool.New = func() interface{} {
   213  		ps := make(Params, 0, 1)
   214  		return &ps
   215  	}
   216  	return r
   217  }
   218  
   219  func (r *Router) NewGroup(path string) *RouteGroup {
   220  	return newRouteGroup(r, path)
   221  }
   222  
   223  func (r *Router) getParams() *Params {
   224  	ps, _ := r.paramsPool.Get().(*Params)
   225  	*ps = (*ps)[0:0] // reset slice
   226  	return ps
   227  }
   228  
   229  func (r *Router) putParams(ps *Params) {
   230  	if ps != nil {
   231  		r.paramsPool.Put(ps)
   232  	}
   233  }
   234  
   235  func (r *Router) saveMatchedRoutePath(name string, handle Handle) Handle {
   236  	return func(w http.ResponseWriter, req *http.Request, ps Params) {
   237  		if ps == nil {
   238  			psp := r.getParams()
   239  			ps = (*psp)[0:1]
   240  			ps[0] = Param{Key: MatchedRouteNameParam, Value: name}
   241  			handle(w, req, ps)
   242  			r.putParams(psp)
   243  		} else {
   244  			ps = append(ps, Param{Key: MatchedRouteNameParam, Value: name})
   245  			handle(w, req, ps)
   246  		}
   247  	}
   248  }
   249  
   250  // GET is a shortcut for router.Handle(http.MethodGet, path, handle)
   251  func (r *Router) GET(path string, handle Handle) {
   252  	r.Handle(http.MethodGet, path, handle)
   253  }
   254  
   255  // HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
   256  func (r *Router) HEAD(path string, handle Handle) {
   257  	r.Handle(http.MethodHead, path, handle)
   258  }
   259  
   260  // OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
   261  func (r *Router) OPTIONS(path string, handle Handle) {
   262  	r.Handle(http.MethodOptions, path, handle)
   263  }
   264  
   265  // POST is a shortcut for router.Handle(http.MethodPost, path, handle)
   266  func (r *Router) POST(path string, handle Handle) {
   267  	r.Handle(http.MethodPost, path, handle)
   268  }
   269  
   270  // PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
   271  func (r *Router) PUT(path string, handle Handle) {
   272  	r.Handle(http.MethodPut, path, handle)
   273  }
   274  
   275  // PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
   276  func (r *Router) PATCH(path string, handle Handle) {
   277  	r.Handle(http.MethodPatch, path, handle)
   278  }
   279  
   280  // DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
   281  func (r *Router) DELETE(path string, handle Handle) {
   282  	r.Handle(http.MethodDelete, path, handle)
   283  }
   284  
   285  func (r *Router) methodIndexOf(method string) int {
   286  	switch method {
   287  	case http.MethodGet:
   288  		return 0
   289  	case http.MethodHead:
   290  		return 1
   291  	case http.MethodPost:
   292  		return 2
   293  	case http.MethodPut:
   294  		return 3
   295  	case http.MethodPatch:
   296  		return 4
   297  	case http.MethodDelete:
   298  		return 5
   299  	case http.MethodConnect:
   300  		return 6
   301  	case http.MethodOptions:
   302  		return 7
   303  	case http.MethodTrace:
   304  		return 8
   305  	}
   306  	return -1
   307  }
   308  
   309  func path2key(method, path string) string {
   310  	var sb strings.Builder
   311  	sb.WriteString(method)
   312  	sb.WriteString(":")
   313  	sb.WriteString(path)
   314  	return sb.String()
   315  }
   316  
   317  func countParams(path string) uint16 {
   318  	var n uint
   319  	for i := range []byte(path) {
   320  		switch path[i] {
   321  		case ':', '*':
   322  			n++
   323  		}
   324  	}
   325  	return uint16(n)
   326  }
   327  
   328  // Handle registers a new request handle with the given path and method.
   329  //
   330  // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
   331  // functions can be used.
   332  //
   333  // This function is intended for bulk loading and to allow the usage of less
   334  // frequently used, non-standardized or custom methods (e.g. for internal
   335  // communication with a proxy).
   336  func (r *Router) Handle(method, path string, handle Handle, name ...string) {
   337  	switch {
   338  	case len(method) == 0:
   339  		panic("method must not be empty")
   340  	case len(path) < 1 || path[0] != '/':
   341  		panic("path must begin with '/' in path '" + path + "'")
   342  	case handle == nil:
   343  		panic("handler must not be nil")
   344  	}
   345  	idx := r.methodIndexOf(method)
   346  	if idx < 0 {
   347  		panic("unknown http method")
   348  	}
   349  	_, f := r.registeredPaths[method]
   350  	r.registeredPaths[method] = append(r.registeredPaths[method], path)
   351  	if !f {
   352  		r.globalAllowed = r.allowed("*", "")
   353  	}
   354  	if r.SaveMatchedRoutePath {
   355  		if len(name) == 0 {
   356  			panic("route name must not be nil")
   357  		}
   358  		handle = r.saveMatchedRoutePath(name[0], handle)
   359  	}
   360  	if strings.Contains(path, "*") {
   361  		pt := urlpath.New(path)
   362  		r.dynamicHandlers[idx][&pt] = handle
   363  	} else {
   364  		r.handlers[path2key(method, path)] = handle
   365  	}
   366  }
   367  
   368  // Handler is an adapter which allows the usage of an http.Handler as a
   369  // request handle.
   370  // The Params are available in the request context under ParamsKey.
   371  func (r *Router) Handler(method, path string, handler http.Handler, name ...string) {
   372  	r.Handle(method, path,
   373  		func(w http.ResponseWriter, req *http.Request, p Params) {
   374  			if len(p) > 0 {
   375  				req = req.WithContext(context.WithValue(req.Context(), ParamsKey, p))
   376  			}
   377  			handler.ServeHTTP(w, req)
   378  		},
   379  		name...,
   380  	)
   381  }
   382  
   383  // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
   384  // request handle.
   385  func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc, name ...string) {
   386  	r.Handler(method, path, handler, name...)
   387  }
   388  
   389  func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
   390  	if rcv := recover(); rcv != nil {
   391  		r.PanicHandler(w, req, rcv)
   392  	}
   393  }
   394  
   395  func (r *Router) search(method, path string) Handle {
   396  	idx := r.methodIndexOf(method)
   397  	if idx < 0 {
   398  		return nil
   399  	}
   400  	for k := range r.dynamicHandlers[idx] {
   401  		if _, ok := k.Match(path); !ok {
   402  			continue
   403  		}
   404  		return r.dynamicHandlers[idx][k]
   405  	}
   406  	return nil
   407  }
   408  
   409  func (r *Router) allowed(path, reqMethod string) (allow string) {
   410  	allowed := make([]string, 0, 9)
   411  
   412  	if path == "*" || path == "/*" { // server-wide{ // server-wide
   413  		// empty method is used for internal calls to refresh the cache
   414  		if reqMethod == "" {
   415  			for method := range r.registeredPaths {
   416  				if method == http.MethodOptions {
   417  					continue
   418  				}
   419  				// Add request method to list of allowed methods
   420  				allowed = append(allowed, method)
   421  			}
   422  		} else {
   423  			return r.globalAllowed
   424  		}
   425  	} else { // specific path
   426  		for method := range r.registeredPaths {
   427  			// Skip the requested method - we already tried this one
   428  			if method == reqMethod || method == http.MethodOptions {
   429  				continue
   430  			}
   431  
   432  			handle, _ := r.handlers[path2key(method, path)]
   433  			if handle == nil {
   434  				handle = r.search(method, path)
   435  			}
   436  			if handle != nil {
   437  				// Add request method to list of allowed methods
   438  				allowed = append(allowed, method)
   439  			}
   440  		}
   441  	}
   442  
   443  	if len(allowed) > 0 {
   444  		// Add request method to list of allowed methods
   445  		allowed = append(allowed, http.MethodOptions)
   446  
   447  		// Sort allowed methods.
   448  		// sort.Strings(allowed) unfortunately causes unnecessary allocations
   449  		// due to allowed being moved to the heap and interface conversion
   450  		for i, l := 1, len(allowed); i < l; i++ {
   451  			for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
   452  				allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
   453  			}
   454  		}
   455  
   456  		// return as comma separated list
   457  		return strings.Join(allowed, ", ")
   458  	}
   459  	return
   460  }
   461  
   462  // ServeHTTP makes the router implement the http.Handler interface.
   463  func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   464  	if r.PanicHandler != nil {
   465  		defer r.recv(w, req)
   466  	}
   467  
   468  	path := req.URL.Path
   469  	method := req.Method
   470  	methodIndex := r.methodIndexOf(method)
   471  
   472  	if methodIndex > -1 {
   473  		handle := r.handlers[path2key(method, path)]
   474  		if handle == nil {
   475  			handle = r.search(method, path)
   476  		}
   477  		if handle != nil {
   478  			handle(w, req, nil)
   479  			return
   480  		}
   481  	}
   482  
   483  	if req.Method == http.MethodOptions && r.HandleOPTIONS {
   484  		// Handle OPTIONS requests
   485  		if allow := r.allowed(path, http.MethodOptions); allow != "" {
   486  			w.Header().Set("Allow", allow)
   487  			if r.GlobalOPTIONS != nil {
   488  				r.GlobalOPTIONS.ServeHTTP(w, req)
   489  			}
   490  			return
   491  		}
   492  	} else if r.HandleMethodNotAllowed { // Handle 405
   493  		if allow := r.allowed(path, req.Method); allow != "" {
   494  			w.Header().Set("Allow", allow)
   495  			if r.MethodNotAllowed != nil {
   496  				r.MethodNotAllowed.ServeHTTP(w, req)
   497  			} else {
   498  				http.Error(w,
   499  					http.StatusText(http.StatusMethodNotAllowed),
   500  					http.StatusMethodNotAllowed,
   501  				)
   502  			}
   503  			return
   504  		}
   505  	}
   506  
   507  	// Handle 404
   508  	if r.NotFound != nil {
   509  		r.NotFound.ServeHTTP(w, req)
   510  	} else {
   511  		http.NotFound(w, req)
   512  	}
   513  }