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

     1  // Copyright 2017-present Kirill Danshin and Gramework contributors
     2  // Copyright 2019-present Highload LTD (UK CN: 11893420)
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  
    11  package gramework
    12  
    13  import (
    14  	"fmt"
    15  	"path"
    16  	"reflect"
    17  	"runtime"
    18  	"strings"
    19  
    20  	"github.com/apex/log"
    21  	"github.com/valyala/fasthttp"
    22  )
    23  
    24  // JSON register internal handler that sets json content type
    25  // and serves given handler with GET method
    26  func (r *Router) JSON(route string, handler interface{}) *Router {
    27  	h := r.determineHandler(handler)
    28  	r.GET(route, jsonHandler(h))
    29  
    30  	return r
    31  }
    32  
    33  // GET registers a handler for a GET request to the given route
    34  func (r *Router) GET(route string, handler interface{}) *Router {
    35  	r.Handle(MethodGET, route, handler)
    36  	return r
    37  }
    38  
    39  // Forbidden serves 403 on route it registered on
    40  func (r *Router) Forbidden(ctx *Context) {
    41  	ctx.Forbidden()
    42  }
    43  
    44  // DELETE registers a handler for a DELETE request to the given route
    45  func (r *Router) DELETE(route string, handler interface{}) *Router {
    46  	r.Handle(MethodDELETE, route, handler)
    47  	return r
    48  }
    49  
    50  // HEAD registers a handler for a HEAD request to the given route
    51  func (r *Router) HEAD(route string, handler interface{}) *Router {
    52  	r.Handle(MethodHEAD, route, handler)
    53  	return r
    54  }
    55  
    56  // OPTIONS registers a handler for a OPTIONS request to the given route
    57  func (r *Router) OPTIONS(route string, handler interface{}) *Router {
    58  	r.Handle(MethodOPTIONS, route, handler)
    59  	return r
    60  }
    61  
    62  // PUT registers a handler for a PUT request to the given route
    63  func (r *Router) PUT(route string, handler interface{}) *Router {
    64  	r.Handle(MethodPUT, route, handler)
    65  	return r
    66  }
    67  
    68  // POST registers a handler for a POST request to the given route
    69  func (r *Router) POST(route string, handler interface{}) *Router {
    70  	r.Handle(MethodPOST, route, handler)
    71  	return r
    72  }
    73  
    74  // PATCH registers a handler for a PATCH request to the given route
    75  func (r *Router) PATCH(route string, handler interface{}) *Router {
    76  	r.Handle(MethodPATCH, route, handler)
    77  	return r
    78  }
    79  
    80  // ServeFile serves a file on a given route
    81  func (r *Router) ServeFile(route, file string) *Router {
    82  	r.Handle(MethodGET, route, func(ctx *Context) {
    83  		ctx.SendFile(file)
    84  	})
    85  	return r
    86  }
    87  
    88  // SPAIndex serves an index file or handler on any unregistered route
    89  func (r *Router) SPAIndex(pathOrHandler interface{}) *Router {
    90  	switch v := pathOrHandler.(type) {
    91  	case string:
    92  		r.NotFound(func(ctx *Context) {
    93  			ctx.HTML()
    94  			ctx.SendFile(v)
    95  		})
    96  	default:
    97  		r.NotFound(r.determineHandler(v))
    98  	}
    99  	return r
   100  }
   101  
   102  // Sub let you quickly register subroutes with given prefix
   103  // like app.Sub("v1").GET("route", "hi"), that give you /v1/route
   104  // registered
   105  func (r *Router) Sub(path string) *SubRouter {
   106  	return &SubRouter{
   107  		prefix:   path,
   108  		parent:   r,
   109  		prefixes: []string{path},
   110  	}
   111  }
   112  
   113  func (r *Router) handleReg(method, route string, handler interface{}, prefixes []string) {
   114  	r.initRouter()
   115  	r.app.internalLog.Debugf("registering %s %s", method, route)
   116  	typedHandler := r.determineHandler(handler)
   117  	for prefix := range r.app.protectedPrefixes {
   118  		if strings.HasPrefix(strings.TrimLeft(route, "/"), strings.TrimLeft(prefix, "/")) {
   119  			r.app.internalLog.
   120  				WithField("route", route).
   121  				WithField("method", method).
   122  				Info("[Gramework Protection] Protection enabled for a new route")
   123  			r.app.protectedEndpoints[route] = struct{}{}
   124  			typedHandler = r.app.protectionMiddleware(typedHandler)
   125  			break
   126  		}
   127  	}
   128  	if path.Clean(route) == "/" {
   129  		ok := r.setRootFastpath(method, staticHandler{
   130  			handle:   typedHandler,
   131  			prefixes: prefixes,
   132  		})
   133  		if ok {
   134  			return
   135  		}
   136  	}
   137  	r.router.Handle(method, route, typedHandler, prefixes)
   138  }
   139  
   140  func (r *Router) getEFuncStrHandler(h func() string) func(*Context) {
   141  	return func(ctx *Context) {
   142  		ctx.Response.SetBodyRaw([]byte(h()))
   143  	}
   144  }
   145  
   146  // substring -> full name
   147  var internalShortcuts = map[string]string{
   148  	"#g.(*App).ServeDirCustom": "#g.(*App).ServeDirCustom",
   149  }
   150  
   151  func handlerName(h interface{}) string {
   152  	v := reflect.ValueOf(h)
   153  	if v.Kind() != reflect.Func {
   154  		return fmt.Sprintf("<raw %T>", h)
   155  	}
   156  	funcDesc := runtime.FuncForPC(v.Pointer())
   157  	file, line := funcDesc.FileLine(v.Pointer())
   158  	pathidx := strings.Index(file, "/go/src/")
   159  	if strings.Contains(file, "/go/src") && len(file) > pathidx+len("/go/src/") {
   160  		file = file[strings.Index(file, "/go/src/")+8:]
   161  	}
   162  	file = strings.Replace(file, "github.com/gramework/gramework/", "#g/", -1)
   163  	file = strings.Replace(file, "github.com/gramework/gramework.", "#g.", -1)
   164  	fName := funcDesc.Name()
   165  	fName = strings.Replace(fName, "github.com/gramework/gramework/", "#g/", -1)
   166  	fName = strings.Replace(fName, "github.com/gramework/gramework.", "#g.", -1)
   167  	for substr, fullName := range internalShortcuts {
   168  		if strings.Contains(fName, substr) {
   169  			fName = fullName
   170  			break
   171  		}
   172  	}
   173  	name := fmt.Sprintf("%s@%s:%v", fName, file, line)
   174  	return name
   175  }
   176  
   177  // Handle registers a new request handle with the given path and method.
   178  // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut functions can be used.
   179  // This function is intended for bulk loading and to allow the usage of less frequently used,
   180  // non-standardized or custom methods (e.g. for internal communication with a proxy).
   181  func (r *Router) Handle(method, route string, handler interface{}) *Router {
   182  	r.app.internalLog.WithFields(log.Fields{
   183  		"handler": handlerName(handler),
   184  		"method":  method,
   185  		"route":   route,
   186  	}).Debug("registering route")
   187  	r.handleReg(method, route, handler, nil)
   188  	return r
   189  }
   190  
   191  func (r *Router) getFmtVHandler(v interface{}) func(*Context) {
   192  	cache := []byte(fmt.Sprintf("%v", v))
   193  	return func(ctx *Context) {
   194  		ctx.Response.SetBodyRaw(cache)
   195  	}
   196  }
   197  
   198  func (r *Router) getStringServer(str string) func(*Context) {
   199  	b := []byte(str)
   200  	return func(ctx *Context) {
   201  		ctx.Response.SetBodyRaw(b)
   202  	}
   203  }
   204  
   205  func (r *Router) getHTMLServer(str HTML) func(*Context) {
   206  	b := []byte(str)
   207  	return func(ctx *Context) {
   208  		if _, err := ctx.HTML().Write(b); err != nil {
   209  			// connection broken
   210  			ctx.Error("", 500)
   211  		}
   212  	}
   213  }
   214  
   215  func (r *Router) getJSONServer(str JSON) func(*Context) {
   216  	b := []byte(str)
   217  	return func(ctx *Context) {
   218  		ctx.SetContentType(jsonCTshort)
   219  		ctx.Response.SetBodyRaw(b)
   220  	}
   221  }
   222  
   223  func (r *Router) getBytesServer(b []byte) func(*Context) {
   224  	return func(ctx *Context) {
   225  		ctx.Response.SetBodyRaw(b)
   226  	}
   227  }
   228  
   229  func (r *Router) getFmtDHandler(v interface{}) func(*Context) {
   230  	const fmtD = "%d"
   231  	res := []byte(fmt.Sprintf(fmtD, v))
   232  	return func(ctx *Context) {
   233  		ctx.Response.SetBodyRaw(res)
   234  	}
   235  }
   236  
   237  func (r *Router) getFmtFHandler(v interface{}) func(*Context) {
   238  	const fmtF = "%f"
   239  	res := []byte(fmt.Sprintf(fmtF, v))
   240  	return func(ctx *Context) {
   241  		ctx.Response.SetBodyRaw(res)
   242  	}
   243  }
   244  
   245  // PanicHandler set a handler for unhandled panics
   246  func (r *Router) PanicHandler(panicHandler func(*Context, interface{})) {
   247  	r.initRouter()
   248  	r.router.PanicHandler = panicHandler
   249  }
   250  
   251  // NotFound set a handler which is called when no matching route is found
   252  func (r *Router) NotFound(notFoundHandler func(*Context)) {
   253  	r.initRouter()
   254  	r.router.NotFound = notFoundHandler
   255  }
   256  
   257  // HandleMethodNotAllowed changes HandleMethodNotAllowed mode in the router
   258  func (r *Router) HandleMethodNotAllowed(newValue bool) (oldValue bool) {
   259  	r.initRouter()
   260  	oldValue = r.router.HandleMethodNotAllowed
   261  	r.router.HandleMethodNotAllowed = newValue
   262  	return
   263  }
   264  
   265  // HandleOPTIONS changes HandleOPTIONS mode in the router
   266  func (r *Router) HandleOPTIONS(newValue bool) (oldValue bool) {
   267  	r.initRouter()
   268  	oldValue = r.router.HandleOPTIONS
   269  	r.router.HandleOPTIONS = newValue
   270  	return
   271  }
   272  
   273  // HTTP router returns a router instance that work only on HTTP requests
   274  func (r *Router) HTTP() *Router {
   275  	if r.root != nil {
   276  		return r.root.HTTP()
   277  	}
   278  	r.mu.Lock()
   279  	if r.httprouter == nil {
   280  		r.httprouter = &Router{
   281  			router: newRouter(),
   282  			app:    r.app,
   283  			root:   r,
   284  		}
   285  	}
   286  	r.mu.Unlock()
   287  
   288  	return r.httprouter
   289  }
   290  
   291  // HTTPS router returns a router instance that work only on HTTPS requests
   292  func (r *Router) HTTPS() *Router {
   293  	if r.root != nil {
   294  		return r.root.HTTPS()
   295  	}
   296  	r.mu.Lock()
   297  	if r.httpsrouter == nil {
   298  		r.httpsrouter = &Router{
   299  			router: newRouter(),
   300  			app:    r.app,
   301  			root:   r,
   302  		}
   303  	}
   304  	r.mu.Unlock()
   305  
   306  	return r.httpsrouter
   307  }
   308  
   309  // ServeFiles serves files from the given file system root.
   310  // The path must end with "/*filepath", files are then served from the local
   311  // path /defined/root/dir/*filepath.
   312  // For example if root is "/etc" and *filepath is "passwd", the local file
   313  // "/etc/passwd" would be served.
   314  // Internally a http.FileServer is used, therefore http.NotFound is used instead
   315  // of the Router's NotFound handler.
   316  //     router.ServeFiles("/src/*filepath", "/var/www")
   317  func (r *Router) ServeFiles(path string, rootPath string) {
   318  	r.router.ServeFiles(path, rootPath, nil)
   319  }
   320  
   321  // Lookup allows the manual lookup of a method + path combo.
   322  // This is e.g. useful to build a framework around this router.
   323  // If the path was found, it returns the handle function and the path parameter
   324  // values. Otherwise the third return value indicates whether a redirection to
   325  // the same path with an extra / without the trailing slash should be performed.
   326  func (r *Router) Lookup(method, path string, ctx *Context) (RequestHandler, bool) {
   327  	if path == "/" {
   328  		h, found := r.getRootFastpath(method)
   329  		if found {
   330  			return h.handle, true
   331  		}
   332  	}
   333  	return r.router.Lookup(method, path, ctx)
   334  }
   335  
   336  // MethodNotAllowed sets MethodNotAllowed handler
   337  func (r *Router) MethodNotAllowed(c func(ctx *Context)) {
   338  	r.router.MethodNotAllowed = c
   339  }
   340  
   341  // Allowed returns Allow header's value used in OPTIONS responses
   342  func (r *Router) Allowed(path, reqMethod string) (allow string) {
   343  	return r.router.Allowed(path, reqMethod)
   344  }
   345  
   346  // Handler makes the router implement the fasthttp.ListenAndServe interface.
   347  func (r *Router) Handler() func(*Context) {
   348  	return r.handler
   349  }
   350  
   351  func (r *Router) setRootFastpath(method string, h staticHandler) (ok bool) {
   352  	methodIdx := r.methodToIdx(method)
   353  	if methodIdx < 0 {
   354  		return false
   355  	}
   356  	r.mu.Lock()
   357  	if r.rootHandler == nil {
   358  		r.rootHandler = make([]staticHandler, 32)
   359  	}
   360  	r.rootHandler[methodIdx] = h
   361  	r.mu.Unlock()
   362  	return true
   363  }
   364  
   365  func (r *Router) getRootFastpath(method string) (h staticHandler, found bool) {
   366  	if r.rootHandler == nil {
   367  		return zeroStaticHandler, false
   368  	}
   369  	methodIdx := r.methodToIdx(method)
   370  	if methodIdx < 0 {
   371  		return zeroStaticHandler, false
   372  	}
   373  
   374  	return r.rootHandler[methodIdx], r.rootHandler[methodIdx].handle != nil
   375  }
   376  
   377  var zeroStaticHandler = staticHandler{}
   378  
   379  func (r *Router) methodToIdx(method string) int {
   380  	return methodToIdx(method)
   381  }
   382  
   383  func methodToIdx(method string) int {
   384  	switch method {
   385  	case GET:
   386  		return 0
   387  	case HEAD:
   388  		return 1
   389  	case OPTIONS:
   390  		return 2
   391  	case POST:
   392  		return 3
   393  	case PUT:
   394  		return 4
   395  	case PATCH:
   396  		return 5
   397  	case DELETE:
   398  		return 6
   399  	case CONNECT:
   400  		return 7
   401  	default:
   402  		return -1
   403  	}
   404  }
   405  
   406  func methodByIdx(method int) string {
   407  	switch method {
   408  	case 0:
   409  		return GET
   410  	case 1:
   411  		return HEAD
   412  	case 2:
   413  		return OPTIONS
   414  	case 3:
   415  		return POST
   416  	case 4:
   417  		return PUT
   418  	case 5:
   419  		return PATCH
   420  	case 6:
   421  		return DELETE
   422  	case 7:
   423  		return CONNECT
   424  	default:
   425  		return "<unknown>"
   426  	}
   427  }
   428  
   429  func (r *Router) handleReq(router *Router, method, path string, ctx *Context) (stop bool) {
   430  	if supported, shouldStop := router.fastpath(method, path, ctx); supported {
   431  		return shouldStop
   432  	}
   433  
   434  	return router.defaultHandlingPath(router, method, path, ctx)
   435  }
   436  
   437  func (r *Router) fastpath(method, path string, ctx *Context) (supported, shouldStop bool) {
   438  	if path == "/" {
   439  		h, found := r.getRootFastpath(method)
   440  		if !found {
   441  			return false, false
   442  		}
   443  
   444  		if h.handle != nil {
   445  			h.handle(ctx)
   446  		} else {
   447  			r.default404(ctx)
   448  		}
   449  		return true, true
   450  	}
   451  
   452  	return false, false
   453  }
   454  
   455  func (r *Router) defaultHandlingPath(router *Router, method, path string, ctx *Context) (stop bool) {
   456  	handler, tsr := router.router.Lookup(method, path, ctx)
   457  	if handler != nil && router.handle(path, method, ctx, handler, tsr, false) {
   458  		return true
   459  	}
   460  	isStatic := r.router.routeIsStatic(method, path)
   461  
   462  	if router.router.RedirectFixedPath {
   463  		if isStatic {
   464  			lowerPath := strings.ToLower(path)
   465  			sh, _, found := router.router.lookupStatic(method, lowerPath)
   466  			if found {
   467  				code := redirectCode
   468  				if method != GET {
   469  					code = temporaryRedirectCode
   470  				}
   471  
   472  				uri := r.pathAppendQueryFromCtx([]byte(sh.originalRoute), ctx)
   473  
   474  				ctx.SetStatusCode(code)
   475  				ctx.Response.Header.SetBytesV("Location", uri)
   476  				return true
   477  			}
   478  		}
   479  
   480  		if root, ok := router.router.Trees[method]; ok && root != nil {
   481  			fixedPath, found := root.FindCaseInsensitivePath(
   482  				CleanPath(path),
   483  				router.router.RedirectTrailingSlash,
   484  			)
   485  
   486  			if found {
   487  				code := redirectCode
   488  				if method != GET {
   489  					code = temporaryRedirectCode
   490  				}
   491  
   492  				uri := r.pathAppendQueryFromCtx([]byte(fixedPath), ctx)
   493  
   494  				ctx.SetStatusCode(code)
   495  				ctx.Response.Header.SetBytesV("Location", uri)
   496  				return true
   497  			}
   498  		}
   499  	}
   500  
   501  	if isStatic {
   502  		if method == OPTIONS {
   503  			// Handle OPTIONS requests
   504  			if r.router.HandleOPTIONS {
   505  				if allow := r.getStaticAllowed(method, path, ctx); len(allow) > 0 {
   506  					ctx.Response.Header.Set(HeaderAllow, allow)
   507  				}
   508  			}
   509  		} else {
   510  			if isStatic {
   511  				if allow := r.getStaticAllowed(method, path, ctx); len(allow) > 0 {
   512  					ctx.Response.Header.Set(HeaderAllow, allow)
   513  					if r.router.MethodNotAllowed != nil {
   514  						r.router.MethodNotAllowed(ctx)
   515  					} else {
   516  						ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
   517  						ctx.SetContentTypeBytes(DefaultContentType)
   518  						ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
   519  					}
   520  					return true
   521  				}
   522  			}
   523  		}
   524  	}
   525  
   526  	if router.router.NotFound != nil {
   527  		router.router.NotFound(ctx)
   528  		return true
   529  	}
   530  	return false
   531  }
   532  
   533  func (r *Router) handler(ctx *Context) {
   534  	path := string(ctx.Path())
   535  	method := string(ctx.Method())
   536  
   537  	switch ctx.IsTLS() {
   538  	case true:
   539  		if r.httpsrouter != nil {
   540  			if r.handleReq(r.httpsrouter, method, path, ctx) {
   541  				return
   542  			}
   543  		}
   544  	case false:
   545  		if r.httprouter != nil {
   546  			if r.handleReq(r.httprouter, method, path, ctx) {
   547  				return
   548  			}
   549  		}
   550  	}
   551  	if r.handleReq(r, method, path, ctx) {
   552  		return
   553  	}
   554  	r.default404(ctx)
   555  }
   556  
   557  func (r *Router) default404(ctx *Context) {
   558  	ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound), fasthttp.StatusNotFound)
   559  }
   560  
   561  // pathAppendQueryFromCtx append query string to path in bytes
   562  func (r *Router) pathAppendQueryFromCtx(path []byte, ctx *Context) []byte {
   563  	queryBuf := ctx.URI().QueryString()
   564  	if len(queryBuf) > zero {
   565  		path = append(path, QuestionMark...)
   566  		path = append(path, queryBuf...)
   567  	}
   568  
   569  	return path
   570  }
   571  
   572  // pathAppendQueryFromCtx append query string to path in bytes
   573  func (r *Router) trimTrailingSlash(path string) (string, bool) {
   574  	if len(path) > one && path[len(path)-one] == SlashByte {
   575  		return path[:len(path)-one], true
   576  	}
   577  
   578  	return path, false
   579  }
   580  
   581  func (r *Router) getStaticAllowed(method, path string, ctx *Context) string {
   582  	allowed := ""
   583  	for m, p := range r.router.StaticHandlers {
   584  		if m == methodToIdx(OPTIONS) {
   585  			continue
   586  		}
   587  
   588  		found := false
   589  		if _, ok := p[path]; ok {
   590  			found = true
   591  		} else if _, ok := p[strings.ToLower(path)]; ok {
   592  			found = true
   593  		}
   594  		if found {
   595  			if len(allowed) > 0 {
   596  				allowed += ", "
   597  			}
   598  			allowed += methodByIdx(m)
   599  		}
   600  	}
   601  	if allowed != "" {
   602  		allowed += ", OPTIONS"
   603  	}
   604  	return allowed
   605  }
   606  
   607  func (r *Router) handle(path, method string, ctx *Context, handler func(ctx *Context), redirectTrailingSlashs bool, isRootRouter bool) (handlerFound bool) {
   608  	if r.router.PanicHandler != nil {
   609  		defer r.router.Recv(ctx, nil)
   610  	}
   611  
   612  	isStatic := r.router.routeIsStatic(method, path)
   613  
   614  	if isStatic {
   615  		if f, ok := r.router.StaticHandlers[methodToIdx(method)][path]; ok {
   616  			ctx.subPrefixes = f.prefixes
   617  			f.handle(ctx)
   618  			return true
   619  		}
   620  	}
   621  
   622  	if method != CONNECT && path != PathSlash {
   623  		code := redirectCode // Permanent redirect, request with GET method
   624  		if method != GET {
   625  			// Temporary redirect, request with same method
   626  			// As of Go 1.3, Go does not support status code 308.
   627  			code = temporaryRedirectCode
   628  		}
   629  
   630  		if r.router.RedirectTrailingSlash {
   631  			if fixedPath, ok := r.trimTrailingSlash(path); ok {
   632  				if _, ok := r.router.StaticHandlers[methodToIdx(method)][fixedPath]; ok {
   633  					uri := r.pathAppendQueryFromCtx([]byte(fixedPath), ctx)
   634  
   635  					ctx.SetStatusCode(code)
   636  					ctx.Response.Header.SetBytesV("Location", uri)
   637  					return ok
   638  				}
   639  			}
   640  		}
   641  
   642  		if r.router.RedirectFixedPath {
   643  			fixedPath := CleanPath(strings.ToLower(path))
   644  
   645  			if r.router.RedirectTrailingSlash {
   646  				fixedPath, _ = r.trimTrailingSlash(fixedPath)
   647  			}
   648  
   649  			if isStatic {
   650  				if _, ok := r.router.StaticHandlers[methodToIdx(method)][fixedPath]; ok {
   651  					uri := r.pathAppendQueryFromCtx([]byte(fixedPath), ctx)
   652  
   653  					ctx.SetStatusCode(code)
   654  					ctx.Response.Header.SetBytesV("Location", uri)
   655  					return true
   656  				}
   657  			}
   658  		}
   659  	}
   660  
   661  	if root := r.router.Trees[method]; root != nil {
   662  		if f, prefixes, tsr := root.GetValue(path, ctx, string(ctx.Method())); f != nil {
   663  			ctx.subPrefixes = prefixes
   664  			f(ctx)
   665  			return true
   666  		} else if method != CONNECT && path != PathSlash {
   667  			code := redirectCode // Permanent redirect, request with GET method
   668  			if method != GET {
   669  				// Temporary redirect, request with same method
   670  				// As of Go 1.3, Go does not support status code 308.
   671  				code = temporaryRedirectCode
   672  			}
   673  
   674  			if tsr && r.router.RedirectTrailingSlash {
   675  				if trimmedPath, ok := r.trimTrailingSlash(path); ok {
   676  					fixedPath := r.pathAppendQueryFromCtx([]byte(trimmedPath), ctx)
   677  
   678  					ctx.SetStatusCode(code)
   679  					ctx.Response.Header.SetBytesV("Location", fixedPath)
   680  					return false
   681  				}
   682  			}
   683  
   684  			// Try to fix the request path
   685  			if r.router.RedirectFixedPath {
   686  				fixedPath, found := root.FindCaseInsensitivePath(
   687  					CleanPath(path),
   688  					r.router.RedirectTrailingSlash,
   689  				)
   690  
   691  				if found && len(fixedPath) > 0 {
   692  					fixedPath = r.pathAppendQueryFromCtx(fixedPath, ctx)
   693  					ctx.SetStatusCode(code)
   694  					ctx.Response.Header.SetBytesV("Location", fixedPath)
   695  					return true
   696  				}
   697  			}
   698  		}
   699  	}
   700  
   701  	if !isRootRouter {
   702  		return false
   703  	}
   704  
   705  	if method == OPTIONS {
   706  		// Handle OPTIONS requests
   707  		if r.router.HandleOPTIONS {
   708  			if isStatic {
   709  				if allow := r.getStaticAllowed(method, path, ctx); len(allow) > 0 {
   710  					ctx.Response.Header.Set(HeaderAllow, allow)
   711  				}
   712  			}
   713  			if allow := r.router.Allowed(path, method); len(allow) > zero {
   714  				ctx.Response.Header.Set(HeaderAllow, allow)
   715  				return true
   716  			}
   717  		}
   718  	} else {
   719  		if isStatic {
   720  			if allow := r.getStaticAllowed(method, path, ctx); len(allow) > 0 {
   721  				ctx.Response.Header.Set(HeaderAllow, allow)
   722  				if r.router.MethodNotAllowed != nil {
   723  					r.router.MethodNotAllowed(ctx)
   724  				} else {
   725  					ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
   726  					ctx.SetContentTypeBytes(DefaultContentType)
   727  					ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
   728  				}
   729  				return true
   730  			}
   731  		}
   732  		// Handle 405
   733  		if r.router.HandleMethodNotAllowed {
   734  			if allow := r.router.Allowed(path, method); len(allow) > zero {
   735  				ctx.Response.Header.Set(HeaderAllow, allow)
   736  				if r.router.MethodNotAllowed != nil {
   737  					r.router.MethodNotAllowed(ctx)
   738  				} else {
   739  					ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
   740  					ctx.SetContentTypeBytes(DefaultContentType)
   741  					ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
   742  				}
   743  				return true
   744  			}
   745  		}
   746  	}
   747  
   748  	return false
   749  }
   750  
   751  // Redir sends 301 redirect to the given url
   752  //
   753  // it's equivalent to
   754  //
   755  //     ctx.Redirect(url, 301)
   756  func (r *Router) Redir(route, url string) {
   757  	r.GET(route, func(ctx *Context) {
   758  		ctx.Redirect(route, redirectCode)
   759  	})
   760  }