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 // }