github.com/gramework/gramework@v1.8.1-0.20231027140105-82555c9057f5/fasthttprouter_router.go (about)

     1  // Copyright 2013 Julien Schmidt. All rights reserved.
     2  // Copyright (c) 2015-2016, 招牌疯子
     3  // Copyright (c) 2017, Kirill Danshin
     4  // Use of this source code is governed by a BSD-style license that can be found
     5  // in the 3rd-Party License/fasthttprouter file.
     6  
     7  package gramework
     8  
     9  import (
    10  	"strings"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/valyala/fasthttp"
    14  )
    15  
    16  type staticHandler struct {
    17  	handle        RequestHandler
    18  	prefixes      []string
    19  	originalRoute string
    20  }
    21  
    22  type methodIndex = int
    23  
    24  // Router is a http.Handler which can be used to dispatch requests to different
    25  // handler functions via configurable routes
    26  type router struct {
    27  	Trees          map[string]*node
    28  	StaticHandlers map[methodIndex]map[string]staticHandler
    29  
    30  	// Enables automatic redirection if the current route can't be matched but a
    31  	// handler for the path with (without) the trailing slash exists.
    32  	// For example if /foo/ is requested but a route only exists for /foo, the
    33  	// client is redirected to /foo with http status code 301 for GET requests
    34  	// and 307 for all other request methods.
    35  	RedirectTrailingSlash bool
    36  
    37  	// If enabled, the router tries to fix the current request path, if no
    38  	// handle is registered for it.
    39  	// First superfluous path elements like ../ or // are removed.
    40  	// Afterwards the router does a case-insensitive lookup of the cleaned path.
    41  	// If a handle can be found for this route, the router makes a redirection
    42  	// to the corrected path with status code 301 for GET requests and 307 for
    43  	// all other request methods.
    44  	// For example /FOO and /..//Foo could be redirected to /foo.
    45  	// RedirectTrailingSlash is independent of this option.
    46  	RedirectFixedPath bool
    47  
    48  	// If enabled, the router checks if another method is allowed for the
    49  	// current route, if the current request can not be routed.
    50  	// If this is the case, the request is answered with 'Method Not Allowed'
    51  	// and HTTP status code 405.
    52  	// If no other Method is allowed, the request is delegated to the NotFound
    53  	// handler.
    54  	HandleMethodNotAllowed bool
    55  
    56  	// If enabled, the router automatically replies to OPTIONS requests.
    57  	// Custom OPTIONS handlers take priority over automatic replies.
    58  	HandleOPTIONS bool
    59  
    60  	// Configurable http.Handler which is called when no matching route is
    61  	// found. If it is not set, http.NotFound is used.
    62  	NotFound RequestHandler
    63  
    64  	// Configurable http.Handler which is called when a request
    65  	// cannot be routed and HandleMethodNotAllowed is true.
    66  	// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
    67  	// The "Allow" header with allowed request methods is set before the handler
    68  	// is called.
    69  	MethodNotAllowed RequestHandler
    70  
    71  	// Function to handle panics recovered from http handlers.
    72  	// It should be used to generate a error page and return the http error code
    73  	// 500 (Internal Server Error).
    74  	// The handler can be used to keep your server from crashing because of
    75  	// unrecovered panics.
    76  	PanicHandler func(*Context, interface{})
    77  
    78  	cache *cache
    79  }
    80  
    81  const (
    82  	// GET method
    83  	GET = "GET"
    84  	// HEAD method
    85  	HEAD = "HEAD"
    86  	// OPTIONS method
    87  	OPTIONS = "OPTIONS"
    88  	// POST method
    89  	POST = "POST"
    90  	// PUT method
    91  	PUT = "PUT"
    92  	// PATCH method
    93  	PATCH = "PATCH"
    94  	// DELETE method
    95  	DELETE = "DELETE"
    96  	// CONNECT method
    97  	CONNECT = "CONNECT"
    98  
    99  	// PathAny used to minimize memory allocations
   100  	PathAny = "*"
   101  	// PathSlashAny used to minimize memory allocations
   102  	PathSlashAny = "/*"
   103  	// PathSlash used to minimize memory allocations
   104  	PathSlash = "/"
   105  
   106  	// HeaderAllow used to minimize memory allocations
   107  	HeaderAllow = "Allow"
   108  )
   109  
   110  var (
   111  	// DefaultContentType cached to minimize memory allocations
   112  	DefaultContentType = []byte("text/plain; charset=utf-8")
   113  	// QuestionMark cached to minimize memory allocations
   114  	QuestionMark = []byte("?")
   115  
   116  	// SlashByte cached to minimize memory allocations
   117  	SlashByte = byte('/')
   118  )
   119  
   120  // newRouter returns a new initialized Router.
   121  // Path auto-correction, including trailing slashes, is enabled by default.
   122  func newRouter() *router {
   123  	r := &router{
   124  		RedirectTrailingSlash:  true,
   125  		RedirectFixedPath:      true,
   126  		HandleMethodNotAllowed: true,
   127  		HandleOPTIONS:          true,
   128  		cache: &cache{
   129  			v: map[string]*msc{
   130  				// init default methods
   131  				MethodGET: &msc{
   132  					v: make(map[string]*cacheRecord),
   133  				},
   134  				MethodDELETE: &msc{
   135  					v: make(map[string]*cacheRecord),
   136  				},
   137  				MethodHEAD: &msc{
   138  					v: make(map[string]*cacheRecord),
   139  				},
   140  				MethodOPTIONS: &msc{
   141  					v: make(map[string]*cacheRecord),
   142  				},
   143  				MethodPATCH: &msc{
   144  					v: make(map[string]*cacheRecord),
   145  				},
   146  				MethodPOST: &msc{
   147  					v: make(map[string]*cacheRecord),
   148  				},
   149  				MethodPUT: &msc{
   150  					v: make(map[string]*cacheRecord),
   151  				},
   152  			},
   153  		},
   154  	}
   155  	r.StaticHandlers = map[methodIndex]map[string]staticHandler{
   156  		methodToIdx(GET):     make(map[string]staticHandler),
   157  		methodToIdx(DELETE):  make(map[string]staticHandler),
   158  		methodToIdx(HEAD):    make(map[string]staticHandler),
   159  		methodToIdx(OPTIONS): make(map[string]staticHandler),
   160  		methodToIdx(PATCH):   make(map[string]staticHandler),
   161  		methodToIdx(POST):    make(map[string]staticHandler),
   162  		methodToIdx(PUT):     make(map[string]staticHandler),
   163  	}
   164  	go r.cache.maintain()
   165  	return r
   166  }
   167  
   168  // GET is a shortcut for router.Handle("GET", path, handle)
   169  func (r *router) GET(path string, handle RequestHandler, prefixes []string) {
   170  	r.Handle(GET, path, handle, prefixes)
   171  }
   172  
   173  // HEAD is a shortcut for router.Handle("HEAD", path, handle)
   174  func (r *router) HEAD(path string, handle RequestHandler, prefixes []string) {
   175  	r.Handle(HEAD, path, handle, prefixes)
   176  }
   177  
   178  // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
   179  func (r *router) OPTIONS(path string, handle RequestHandler, prefixes []string) {
   180  	r.Handle(OPTIONS, path, handle, prefixes)
   181  }
   182  
   183  // POST is a shortcut for router.Handle("POST", path, handle)
   184  func (r *router) POST(path string, handle RequestHandler, prefixes []string) {
   185  	r.Handle(POST, path, handle, prefixes)
   186  }
   187  
   188  // PUT is a shortcut for router.Handle("PUT", path, handle)
   189  func (r *router) PUT(path string, handle RequestHandler, prefixes []string) {
   190  	r.Handle(PUT, path, handle, prefixes)
   191  }
   192  
   193  // PATCH is a shortcut for router.Handle("PATCH", path, handle)
   194  func (r *router) PATCH(path string, handle RequestHandler, prefixes []string) {
   195  	r.Handle(PATCH, path, handle, prefixes)
   196  }
   197  
   198  // DELETE is a shortcut for router.Handle("DELETE", path, handle)
   199  func (r *router) DELETE(path string, handle RequestHandler, prefixes []string) {
   200  	r.Handle(DELETE, path, handle, prefixes)
   201  }
   202  
   203  func (r *router) routeIsStatic(method, path string) (isStatic bool) {
   204  	isStatic = false
   205  	for _, sym := range path {
   206  		if sym == '*' || sym == ':' {
   207  			return
   208  		}
   209  	}
   210  	return true
   211  }
   212  
   213  // Handle registers a new request handle with the given path and method.
   214  //
   215  // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
   216  // functions can be used.
   217  //
   218  // This function is intended for bulk loading and to allow the usage of less
   219  // frequently used, non-standardized or custom methods (e.g. for internal
   220  // communication with a proxy).
   221  func (r *router) Handle(method, path string, handle RequestHandler, prefixes []string) {
   222  	if path[0] != SlashByte {
   223  		panic("path must begin with '/' in path '" + path + "'")
   224  	}
   225  
   226  	if path != Slash {
   227  		path = strings.TrimRight(path, Slash)
   228  	}
   229  
   230  	if r.routeIsStatic(method, path) {
   231  		if r.StaticHandlers[methodToIdx(method)] == nil {
   232  			r.StaticHandlers[methodToIdx(method)] = make(map[string]staticHandler)
   233  		}
   234  		sh := staticHandler{
   235  			handle:        handle,
   236  			prefixes:      prefixes,
   237  			originalRoute: path,
   238  		}
   239  		r.StaticHandlers[methodToIdx(method)][path] = sh
   240  		r.StaticHandlers[methodToIdx(method)][strings.ToLower(path)] = sh
   241  		return
   242  	}
   243  
   244  	if r.Trees == nil {
   245  		r.Trees = make(map[string]*node)
   246  	}
   247  
   248  	root := r.Trees[method]
   249  	if root == nil {
   250  		root = new(node)
   251  		root.router = r
   252  		r.Trees[method] = root
   253  	}
   254  
   255  	root.addRoute(path, handle, r, prefixes)
   256  }
   257  
   258  // ServeFiles serves files from the given file system root.
   259  // The path must end with "/*filepath", files are then served from the local
   260  // path /defined/root/dir/*filepath.
   261  // For example if root is "/etc" and *filepath is "passwd", the local file
   262  // "/etc/passwd" would be served.
   263  // Internally a http.FileServer is used, therefore http.NotFound is used instead
   264  // of the Router's NotFound handler.
   265  //     router.ServeFiles("/src/*filepath", "/var/www")
   266  func (r *router) ServeFiles(path string, rootPath string, prefixes []string) {
   267  	if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
   268  		panic("path must end with /*filepath in path '" + path + "'")
   269  	}
   270  	prefix := path[:len(path)-10]
   271  
   272  	fileHandler := fasthttp.FSHandler(rootPath, strings.Count(prefix, PathSlash))
   273  
   274  	r.GET(path, func(ctx *Context) {
   275  		fileHandler(ctx.RequestCtx)
   276  	}, prefixes)
   277  }
   278  
   279  // Recv used to recover after panic. Called if PanicHandler was set
   280  func (r *router) Recv(ctx *Context, tracer *log.Entry) {
   281  	if rcv := recover(); rcv != nil {
   282  		if r.PanicHandler != nil {
   283  			r.PanicHandler(ctx, rcv)
   284  		} else {
   285  			DefaultPanicHandler(ctx, rcv)
   286  		}
   287  		if tracer != nil {
   288  			tracer.WithFields(log.Fields{
   289  				"reason": rcv,
   290  				"code":   ctx.Response.StatusCode(),
   291  			}).Error("request caused panic")
   292  		}
   293  	}
   294  }
   295  
   296  func (r *router) lookupStatic(method, path string) (h staticHandler, tsr, found bool) {
   297  	if paths, ok := r.StaticHandlers[methodToIdx(method)]; ok {
   298  		if handler, ok := paths[strings.TrimRight(path, "/")]; ok {
   299  			return handler, false, true
   300  		}
   301  		if _, ok := paths[path+"/"]; ok {
   302  			return zeroStaticHandler, true, true
   303  		}
   304  	}
   305  
   306  	return zeroStaticHandler, false, false
   307  }
   308  
   309  // Lookup allows the manual lookup of a method + path combo.
   310  // This is e.g. useful to build a framework around this router.
   311  // If the path was found, it returns the handle function and the path parameter
   312  // values. Otherwise the third return value indicates whether a redirection to
   313  // the same path with an extra / without the trailing slash should be performed.
   314  func (r *router) Lookup(method, path string, ctx *Context) (RequestHandler, bool) {
   315  	if r.routeIsStatic(method, path) {
   316  		if sh, tsr, found := r.lookupStatic(method, path); found {
   317  			return sh.handle, tsr
   318  		}
   319  	}
   320  	if root := r.Trees[method]; root != nil {
   321  		node, _, tsr := root.GetValue(path, ctx, method)
   322  		if node != nil {
   323  			return node, tsr
   324  		}
   325  		if tsr {
   326  			return nil, tsr
   327  		}
   328  	}
   329  	return nil, false
   330  }
   331  
   332  // Allowed returns Allow header's value used in OPTIONS responses
   333  func (r *router) Allowed(path, reqMethod string) (allow string) {
   334  	if path == PathAny || path == PathSlashAny { // server-wide
   335  		for method := range r.StaticHandlers {
   336  			if method == methodToIdx(OPTIONS) {
   337  				continue
   338  			}
   339  
   340  			// add request method to list of allowed methods
   341  			if len(allow) == 0 {
   342  				allow = methodByIdx(method)
   343  			} else {
   344  				allow += ", " + methodByIdx(method)
   345  			}
   346  		}
   347  
   348  		for method := range r.Trees {
   349  			if method == OPTIONS {
   350  				continue
   351  			}
   352  
   353  			// add request method to list of allowed methods
   354  			if len(allow) == 0 {
   355  				allow = method
   356  			} else {
   357  				allow += ", " + method
   358  			}
   359  		}
   360  	} else { // specific path
   361  		for method, paths := range r.StaticHandlers {
   362  			// static methods first
   363  			if method == methodToIdx(reqMethod) || method == methodToIdx(OPTIONS) {
   364  				continue
   365  			}
   366  
   367  			if hander, ok := paths[path]; ok && hander.handle != nil {
   368  				// add request method to list of allowed methods
   369  				if len(allow) == 0 {
   370  					allow = methodByIdx(method)
   371  				} else {
   372  					allow += ", " + methodByIdx(method)
   373  				}
   374  			}
   375  		}
   376  
   377  		for method := range r.Trees {
   378  			// Skip the requested method - we already tried this one
   379  			if method == reqMethod || method == OPTIONS {
   380  				continue
   381  			}
   382  
   383  			handle, _, _ := r.Trees[method].GetValue(path, nil, reqMethod)
   384  			if handle != nil {
   385  				// add request method to list of allowed methods
   386  				if len(allow) == 0 {
   387  					allow = method
   388  				} else {
   389  					allow += ", " + method
   390  				}
   391  			}
   392  		}
   393  	}
   394  	if len(allow) > 0 {
   395  		allow += ", OPTIONS"
   396  	}
   397  	return
   398  }
   399  
   400  // // Handler makes the router implement the fasthttp.ListenAndServe interface.
   401  // func (r *router) Handler(ctx *Context) {
   402  // 	if r.PanicHandler != nil {
   403  // 		defer r.Recv(ctx)
   404  // 	}
   405  
   406  // 	path := string(ctx.Path())
   407  // 	method := string(ctx.Method())
   408  // 	if root := r.Trees[method]; root != nil {
   409  // 		if f, tsr := root.GetValue(path, ctx); f != nil {
   410  // 			f(ctx)
   411  // 			return
   412  // 		} else if method != CONNECT && path != PathSlash {
   413  // 			code := 301 // Permanent redirect, request with GET method
   414  // 			if method != GET {
   415  // 				// Temporary redirect, request with same method
   416  // 				// As of Go 1.3, Go does not support status code 308.
   417  // 				code = 307
   418  // 			}
   419  
   420  // 			if tsr && r.RedirectTrailingSlash {
   421  // 				var uri string
   422  // 				if len(path) > 1 && path[len(path)-1] == SlashByte {
   423  // 					uri = path[:len(path)-1]
   424  // 				} else {
   425  // 					uri = path + PathSlash
   426  // 				}
   427  // 				ctx.Redirect(uri, code)
   428  // 				return
   429  // 			}
   430  
   431  // 			// Try to fix the request path
   432  // 			if r.RedirectFixedPath {
   433  // 				fixedPath, found := root.FindCaseInsensitivePath(
   434  // 					CleanPath(path),
   435  // 					r.RedirectTrailingSlash,
   436  // 				)
   437  
   438  // 				if found {
   439  // 					queryBuf := ctx.URI().QueryString()
   440  // 					if len(queryBuf) > 0 {
   441  // 						fixedPath = append(fixedPath, QuestionMark...)
   442  // 						fixedPath = append(fixedPath, queryBuf...)
   443  // 					}
   444  // 					uri := string(fixedPath)
   445  // 					ctx.Redirect(uri, code)
   446  // 					return
   447  // 				}
   448  // 			}
   449  // 		}
   450  // 	}
   451  
   452  // 	if method == OPTIONS {
   453  // 		// Handle OPTIONS requests
   454  // 		if r.HandleOPTIONS {
   455  // 			if allow := r.Allowed(path, method); len(allow) > 0 {
   456  // 				ctx.Response.Header.Set(HeaderAllow, allow)
   457  // 				return
   458  // 			}
   459  // 		}
   460  // 	} else {
   461  // 		// Handle 405
   462  // 		if r.HandleMethodNotAllowed {
   463  // 			if allow := r.Allowed(path, method); len(allow) > 0 {
   464  // 				ctx.Response.Header.Set(HeaderAllow, allow)
   465  // 				if r.MethodNotAllowed != nil {
   466  // 					r.MethodNotAllowed(ctx)
   467  // 				} else {
   468  // 					ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
   469  // 					ctx.SetContentTypeBytes(DefaultContentType)
   470  // 					ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
   471  // 				}
   472  // 				return
   473  // 			}
   474  // 		}
   475  // 	}
   476  
   477  // 	// Handle 404
   478  // 	if r.NotFound != nil {
   479  // 		r.NotFound(ctx)
   480  // 	} else {
   481  // 		ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound),
   482  // 			fasthttp.StatusNotFound)
   483  // 	}
   484  // }