github.com/gofiber/fiber/v2@v2.47.0/app.go (about) 1 // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ 2 // 🤖 Github Repository: https://github.com/gofiber/fiber 3 // 📌 API Documentation: https://docs.gofiber.io 4 5 // Package fiber is an Express inspired web framework built on top of Fasthttp, 6 // the fastest HTTP engine for Go. Designed to ease things up for fast 7 // development with zero memory allocation and performance in mind. 8 package fiber 9 10 import ( 11 "bufio" 12 "context" 13 "encoding/json" 14 "encoding/xml" 15 "errors" 16 "fmt" 17 "log" 18 "net" 19 "net/http" 20 "net/http/httputil" 21 "reflect" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/gofiber/fiber/v2/utils" 28 29 "github.com/valyala/fasthttp" 30 ) 31 32 // Version of current fiber package 33 const Version = "2.47.0" 34 35 // Handler defines a function to serve HTTP requests. 36 type Handler = func(*Ctx) error 37 38 // Map is a shortcut for map[string]interface{}, useful for JSON returns 39 type Map map[string]interface{} 40 41 // Storage interface for communicating with different database/key-value 42 // providers 43 type Storage interface { 44 // Get gets the value for the given key. 45 // `nil, nil` is returned when the key does not exist 46 Get(key string) ([]byte, error) 47 48 // Set stores the given value for the given key along 49 // with an expiration value, 0 means no expiration. 50 // Empty key or value will be ignored without an error. 51 Set(key string, val []byte, exp time.Duration) error 52 53 // Delete deletes the value for the given key. 54 // It returns no error if the storage does not contain the key, 55 Delete(key string) error 56 57 // Reset resets the storage and delete all keys. 58 Reset() error 59 60 // Close closes the storage and will stop any running garbage 61 // collectors and open connections. 62 Close() error 63 } 64 65 // ErrorHandler defines a function that will process all errors 66 // returned from any handlers in the stack 67 // 68 // cfg := fiber.Config{} 69 // cfg.ErrorHandler = func(c *Ctx, err error) error { 70 // code := StatusInternalServerError 71 // var e *fiber.Error 72 // if errors.As(err, &e) { 73 // code = e.Code 74 // } 75 // c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) 76 // return c.Status(code).SendString(err.Error()) 77 // } 78 // app := fiber.New(cfg) 79 type ErrorHandler = func(*Ctx, error) error 80 81 // Error represents an error that occurred while handling a request. 82 type Error struct { 83 Code int `json:"code"` 84 Message string `json:"message"` 85 } 86 87 // App denotes the Fiber application. 88 type App struct { 89 mutex sync.Mutex 90 // Route stack divided by HTTP methods 91 stack [][]*Route 92 // Route stack divided by HTTP methods and route prefixes 93 treeStack []map[string][]*Route 94 // contains the information if the route stack has been changed to build the optimized tree 95 routesRefreshed bool 96 // Amount of registered routes 97 routesCount uint32 98 // Amount of registered handlers 99 handlersCount uint32 100 // Ctx pool 101 pool sync.Pool 102 // Fasthttp server 103 server *fasthttp.Server 104 // App config 105 config Config 106 // Converts string to a byte slice 107 getBytes func(s string) (b []byte) 108 // Converts byte slice to a string 109 getString func(b []byte) string 110 // Hooks 111 hooks *Hooks 112 // Latest route & group 113 latestRoute *Route 114 // TLS handler 115 tlsHandler *TLSHandler 116 // Mount fields 117 mountFields *mountFields 118 // Indicates if the value was explicitly configured 119 configured Config 120 } 121 122 // Config is a struct holding the server settings. 123 type Config struct { 124 // When set to true, this will spawn multiple Go processes listening on the same port. 125 // 126 // Default: false 127 Prefork bool `json:"prefork"` 128 129 // Enables the "Server: value" HTTP header. 130 // 131 // Default: "" 132 ServerHeader string `json:"server_header"` 133 134 // When set to true, the router treats "/foo" and "/foo/" as different. 135 // By default this is disabled and both "/foo" and "/foo/" will execute the same handler. 136 // 137 // Default: false 138 StrictRouting bool `json:"strict_routing"` 139 140 // When set to true, enables case sensitive routing. 141 // E.g. "/FoO" and "/foo" are treated as different routes. 142 // By default this is disabled and both "/FoO" and "/foo" will execute the same handler. 143 // 144 // Default: false 145 CaseSensitive bool `json:"case_sensitive"` 146 147 // When set to true, this relinquishes the 0-allocation promise in certain 148 // cases in order to access the handler values (e.g. request bodies) in an 149 // immutable fashion so that these values are available even if you return 150 // from handler. 151 // 152 // Default: false 153 Immutable bool `json:"immutable"` 154 155 // When set to true, converts all encoded characters in the route back 156 // before setting the path for the context, so that the routing, 157 // the returning of the current url from the context `ctx.Path()` 158 // and the parameters `ctx.Params(%key%)` with decoded characters will work 159 // 160 // Default: false 161 UnescapePath bool `json:"unescape_path"` 162 163 // Enable or disable ETag header generation, since both weak and strong etags are generated 164 // using the same hashing method (CRC-32). Weak ETags are the default when enabled. 165 // 166 // Default: false 167 ETag bool `json:"etag"` 168 169 // Max body size that the server accepts. 170 // -1 will decline any body size 171 // 172 // Default: 4 * 1024 * 1024 173 BodyLimit int `json:"body_limit"` 174 175 // Maximum number of concurrent connections. 176 // 177 // Default: 256 * 1024 178 Concurrency int `json:"concurrency"` 179 180 // Views is the interface that wraps the Render function. 181 // 182 // Default: nil 183 Views Views `json:"-"` 184 185 // Views Layout is the global layout for all template render until override on Render function. 186 // 187 // Default: "" 188 ViewsLayout string `json:"views_layout"` 189 190 // PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine 191 // 192 // Default: false 193 PassLocalsToViews bool `json:"pass_locals_to_views"` 194 195 // The amount of time allowed to read the full request including body. 196 // It is reset after the request handler has returned. 197 // The connection's read deadline is reset when the connection opens. 198 // 199 // Default: unlimited 200 ReadTimeout time.Duration `json:"read_timeout"` 201 202 // The maximum duration before timing out writes of the response. 203 // It is reset after the request handler has returned. 204 // 205 // Default: unlimited 206 WriteTimeout time.Duration `json:"write_timeout"` 207 208 // The maximum amount of time to wait for the next request when keep-alive is enabled. 209 // If IdleTimeout is zero, the value of ReadTimeout is used. 210 // 211 // Default: unlimited 212 IdleTimeout time.Duration `json:"idle_timeout"` 213 214 // Per-connection buffer size for requests' reading. 215 // This also limits the maximum header size. 216 // Increase this buffer if your clients send multi-KB RequestURIs 217 // and/or multi-KB headers (for example, BIG cookies). 218 // 219 // Default: 4096 220 ReadBufferSize int `json:"read_buffer_size"` 221 222 // Per-connection buffer size for responses' writing. 223 // 224 // Default: 4096 225 WriteBufferSize int `json:"write_buffer_size"` 226 227 // CompressedFileSuffix adds suffix to the original file name and 228 // tries saving the resulting compressed file under the new file name. 229 // 230 // Default: ".fiber.gz" 231 CompressedFileSuffix string `json:"compressed_file_suffix"` 232 233 // ProxyHeader will enable c.IP() to return the value of the given header key 234 // By default c.IP() will return the Remote IP from the TCP connection 235 // This property can be useful if you are behind a load balancer: X-Forwarded-* 236 // NOTE: headers are easily spoofed and the detected IP addresses are unreliable. 237 // 238 // Default: "" 239 ProxyHeader string `json:"proxy_header"` 240 241 // GETOnly rejects all non-GET requests if set to true. 242 // This option is useful as anti-DoS protection for servers 243 // accepting only GET requests. The request size is limited 244 // by ReadBufferSize if GETOnly is set. 245 // 246 // Default: false 247 GETOnly bool `json:"get_only"` 248 249 // ErrorHandler is executed when an error is returned from fiber.Handler. 250 // 251 // Default: DefaultErrorHandler 252 ErrorHandler ErrorHandler `json:"-"` 253 254 // When set to true, disables keep-alive connections. 255 // The server will close incoming connections after sending the first response to client. 256 // 257 // Default: false 258 DisableKeepalive bool `json:"disable_keepalive"` 259 260 // When set to true, causes the default date header to be excluded from the response. 261 // 262 // Default: false 263 DisableDefaultDate bool `json:"disable_default_date"` 264 265 // When set to true, causes the default Content-Type header to be excluded from the response. 266 // 267 // Default: false 268 DisableDefaultContentType bool `json:"disable_default_content_type"` 269 270 // When set to true, disables header normalization. 271 // By default all header names are normalized: conteNT-tYPE -> Content-Type. 272 // 273 // Default: false 274 DisableHeaderNormalizing bool `json:"disable_header_normalizing"` 275 276 // When set to true, it will not print out the «Fiber» ASCII art and listening address. 277 // 278 // Default: false 279 DisableStartupMessage bool `json:"disable_startup_message"` 280 281 // This function allows to setup app name for the app 282 // 283 // Default: nil 284 AppName string `json:"app_name"` 285 286 // StreamRequestBody enables request body streaming, 287 // and calls the handler sooner when given body is 288 // larger then the current limit. 289 StreamRequestBody bool 290 291 // Will not pre parse Multipart Form data if set to true. 292 // 293 // This option is useful for servers that desire to treat 294 // multipart form data as a binary blob, or choose when to parse the data. 295 // 296 // Server pre parses multipart form data by default. 297 DisablePreParseMultipartForm bool 298 299 // Aggressively reduces memory usage at the cost of higher CPU usage 300 // if set to true. 301 // 302 // Try enabling this option only if the server consumes too much memory 303 // serving mostly idle keep-alive connections. This may reduce memory 304 // usage by more than 50%. 305 // 306 // Default: false 307 ReduceMemoryUsage bool `json:"reduce_memory_usage"` 308 309 // FEATURE: v2.3.x 310 // The router executes the same handler by default if StrictRouting or CaseSensitive is disabled. 311 // Enabling RedirectFixedPath will change this behavior into a client redirect to the original route path. 312 // Using the status code 301 for GET requests and 308 for all other request methods. 313 // 314 // Default: false 315 // RedirectFixedPath bool 316 317 // When set by an external client of Fiber it will use the provided implementation of a 318 // JSONMarshal 319 // 320 // Allowing for flexibility in using another json library for encoding 321 // Default: json.Marshal 322 JSONEncoder utils.JSONMarshal `json:"-"` 323 324 // When set by an external client of Fiber it will use the provided implementation of a 325 // JSONUnmarshal 326 // 327 // Allowing for flexibility in using another json library for decoding 328 // Default: json.Unmarshal 329 JSONDecoder utils.JSONUnmarshal `json:"-"` 330 331 // XMLEncoder set by an external client of Fiber it will use the provided implementation of a 332 // XMLMarshal 333 // 334 // Allowing for flexibility in using another XML library for encoding 335 // Default: xml.Marshal 336 XMLEncoder utils.XMLMarshal `json:"-"` 337 338 // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) 339 // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose. 340 // 341 // Default: NetworkTCP4 342 Network string 343 344 // If you find yourself behind some sort of proxy, like a load balancer, 345 // then certain header information may be sent to you using special X-Forwarded-* headers or the Forwarded header. 346 // For example, the Host HTTP header is usually used to return the requested host. 347 // But when you’re behind a proxy, the actual host may be stored in an X-Forwarded-Host header. 348 // 349 // If you are behind a proxy, you should enable TrustedProxyCheck to prevent header spoofing. 350 // If you enable EnableTrustedProxyCheck and leave TrustedProxies empty Fiber will skip 351 // all headers that could be spoofed. 352 // If request ip in TrustedProxies whitelist then: 353 // 1. c.Protocol() get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header 354 // 2. c.IP() get value from ProxyHeader header. 355 // 3. c.Hostname() get value from X-Forwarded-Host header 356 // But if request ip NOT in Trusted Proxies whitelist then: 357 // 1. c.Protocol() WON't get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, 358 // will return https in case when tls connection is handled by the app, of http otherwise 359 // 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context 360 // 3. c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host() 361 // will be used to get the hostname. 362 // 363 // Default: false 364 EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check"` 365 366 // Read EnableTrustedProxyCheck doc. 367 // 368 // Default: []string 369 TrustedProxies []string `json:"trusted_proxies"` 370 trustedProxiesMap map[string]struct{} 371 trustedProxyRanges []*net.IPNet 372 373 // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them. 374 // Also, c.IP() will return only the first valid IP rather than just the raw header 375 // WARNING: this has a performance cost associated with it. 376 // 377 // Default: false 378 EnableIPValidation bool `json:"enable_ip_validation"` 379 380 // If set to true, will print all routes with their method, path and handler. 381 // Default: false 382 EnablePrintRoutes bool `json:"enable_print_routes"` 383 384 // You can define custom color scheme. They'll be used for startup message, route list and some middlewares. 385 // 386 // Optional. Default: DefaultColors 387 ColorScheme Colors `json:"color_scheme"` 388 389 // RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. 390 // 391 // Optional. Default: DefaultMethods 392 RequestMethods []string 393 } 394 395 // Static defines configuration options when defining static assets. 396 type Static struct { 397 // When set to true, the server tries minimizing CPU usage by caching compressed files. 398 // This works differently than the github.com/gofiber/compression middleware. 399 // Optional. Default value false 400 Compress bool `json:"compress"` 401 402 // When set to true, enables byte range requests. 403 // Optional. Default value false 404 ByteRange bool `json:"byte_range"` 405 406 // When set to true, enables directory browsing. 407 // Optional. Default value false. 408 Browse bool `json:"browse"` 409 410 // When set to true, enables direct download. 411 // Optional. Default value false. 412 Download bool `json:"download"` 413 414 // The name of the index file for serving a directory. 415 // Optional. Default value "index.html". 416 Index string `json:"index"` 417 418 // Expiration duration for inactive file handlers. 419 // Use a negative time.Duration to disable it. 420 // 421 // Optional. Default value 10 * time.Second. 422 CacheDuration time.Duration `json:"cache_duration"` 423 424 // The value for the Cache-Control HTTP-header 425 // that is set on the file response. MaxAge is defined in seconds. 426 // 427 // Optional. Default value 0. 428 MaxAge int `json:"max_age"` 429 430 // ModifyResponse defines a function that allows you to alter the response. 431 // 432 // Optional. Default: nil 433 ModifyResponse Handler 434 435 // Next defines a function to skip this middleware when returned true. 436 // 437 // Optional. Default: nil 438 Next func(c *Ctx) bool 439 } 440 441 // RouteMessage is some message need to be print when server starts 442 type RouteMessage struct { 443 name string 444 method string 445 path string 446 handlers string 447 } 448 449 // Default Config values 450 const ( 451 DefaultBodyLimit = 4 * 1024 * 1024 452 DefaultConcurrency = 256 * 1024 453 DefaultReadBufferSize = 4096 454 DefaultWriteBufferSize = 4096 455 DefaultCompressedFileSuffix = ".fiber.gz" 456 ) 457 458 // HTTP methods enabled by default 459 var DefaultMethods = []string{ 460 MethodGet, 461 MethodHead, 462 MethodPost, 463 MethodPut, 464 MethodDelete, 465 MethodConnect, 466 MethodOptions, 467 MethodTrace, 468 MethodPatch, 469 } 470 471 // DefaultErrorHandler that process return errors from handlers 472 func DefaultErrorHandler(c *Ctx, err error) error { 473 code := StatusInternalServerError 474 var e *Error 475 if errors.As(err, &e) { 476 code = e.Code 477 } 478 c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) 479 return c.Status(code).SendString(err.Error()) 480 } 481 482 // New creates a new Fiber named instance. 483 // 484 // app := fiber.New() 485 // 486 // You can pass optional configuration options by passing a Config struct: 487 // 488 // app := fiber.New(fiber.Config{ 489 // Prefork: true, 490 // ServerHeader: "Fiber", 491 // }) 492 func New(config ...Config) *App { 493 // Create a new app 494 app := &App{ 495 // Create Ctx pool 496 pool: sync.Pool{ 497 New: func() interface{} { 498 return new(Ctx) 499 }, 500 }, 501 // Create config 502 config: Config{}, 503 getBytes: utils.UnsafeBytes, 504 getString: utils.UnsafeString, 505 latestRoute: &Route{}, 506 } 507 508 // Define hooks 509 app.hooks = newHooks(app) 510 511 // Define mountFields 512 app.mountFields = newMountFields(app) 513 514 // Override config if provided 515 if len(config) > 0 { 516 app.config = config[0] 517 } 518 519 // Initialize configured before defaults are set 520 app.configured = app.config 521 522 if app.config.ETag { 523 if !IsChild() { 524 log.Printf("[Warning] Config.ETag is deprecated since v2.0.6, please use 'middleware/etag'.\n") 525 } 526 } 527 528 // Override default values 529 if app.config.BodyLimit == 0 { 530 app.config.BodyLimit = DefaultBodyLimit 531 } 532 if app.config.Concurrency <= 0 { 533 app.config.Concurrency = DefaultConcurrency 534 } 535 if app.config.ReadBufferSize <= 0 { 536 app.config.ReadBufferSize = DefaultReadBufferSize 537 } 538 if app.config.WriteBufferSize <= 0 { 539 app.config.WriteBufferSize = DefaultWriteBufferSize 540 } 541 if app.config.CompressedFileSuffix == "" { 542 app.config.CompressedFileSuffix = DefaultCompressedFileSuffix 543 } 544 if app.config.Immutable { 545 app.getBytes, app.getString = getBytesImmutable, getStringImmutable 546 } 547 548 if app.config.ErrorHandler == nil { 549 app.config.ErrorHandler = DefaultErrorHandler 550 } 551 552 if app.config.JSONEncoder == nil { 553 app.config.JSONEncoder = json.Marshal 554 } 555 if app.config.JSONDecoder == nil { 556 app.config.JSONDecoder = json.Unmarshal 557 } 558 if app.config.XMLEncoder == nil { 559 app.config.XMLEncoder = xml.Marshal 560 } 561 if app.config.Network == "" { 562 app.config.Network = NetworkTCP4 563 } 564 if len(app.config.RequestMethods) == 0 { 565 app.config.RequestMethods = DefaultMethods 566 } 567 568 app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies)) 569 for _, ipAddress := range app.config.TrustedProxies { 570 app.handleTrustedProxy(ipAddress) 571 } 572 573 // Create router stack 574 app.stack = make([][]*Route, len(app.config.RequestMethods)) 575 app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods)) 576 577 // Override colors 578 app.config.ColorScheme = defaultColors(app.config.ColorScheme) 579 580 // Init app 581 app.init() 582 583 // Return app 584 return app 585 } 586 587 // Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not 588 func (app *App) handleTrustedProxy(ipAddress string) { 589 if strings.Contains(ipAddress, "/") { 590 _, ipNet, err := net.ParseCIDR(ipAddress) 591 if err != nil { 592 log.Printf("[Warning] IP range %q could not be parsed: %v\n", ipAddress, err) 593 } else { 594 app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) 595 } 596 } else { 597 app.config.trustedProxiesMap[ipAddress] = struct{}{} 598 } 599 } 600 601 // SetTLSHandler You can use SetTLSHandler to use ClientHelloInfo when using TLS with Listener. 602 func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { 603 // Attach the tlsHandler to the config 604 app.mutex.Lock() 605 app.tlsHandler = tlsHandler 606 app.mutex.Unlock() 607 } 608 609 // Name Assign name to specific route. 610 func (app *App) Name(name string) Router { 611 app.mutex.Lock() 612 defer app.mutex.Unlock() 613 614 for _, routes := range app.stack { 615 for _, route := range routes { 616 if route.Path == app.latestRoute.path { 617 route.Name = name 618 619 if route.group != nil { 620 route.Name = route.group.name + route.Name 621 } 622 } 623 } 624 } 625 626 if err := app.hooks.executeOnNameHooks(*app.latestRoute); err != nil { 627 panic(err) 628 } 629 630 return app 631 } 632 633 // GetRoute Get route by name 634 func (app *App) GetRoute(name string) Route { 635 for _, routes := range app.stack { 636 for _, route := range routes { 637 if route.Name == name { 638 return *route 639 } 640 } 641 } 642 643 return Route{} 644 } 645 646 // GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. 647 func (app *App) GetRoutes(filterUseOption ...bool) []Route { 648 var rs []Route 649 var filterUse bool 650 if len(filterUseOption) != 0 { 651 filterUse = filterUseOption[0] 652 } 653 for _, routes := range app.stack { 654 for _, route := range routes { 655 if filterUse && route.use { 656 continue 657 } 658 rs = append(rs, *route) 659 } 660 } 661 return rs 662 } 663 664 // Use registers a middleware route that will match requests 665 // with the provided prefix (which is optional and defaults to "/"). 666 // 667 // app.Use(func(c *fiber.Ctx) error { 668 // return c.Next() 669 // }) 670 // app.Use("/api", func(c *fiber.Ctx) error { 671 // return c.Next() 672 // }) 673 // app.Use("/api", handler, func(c *fiber.Ctx) error { 674 // return c.Next() 675 // }) 676 // 677 // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... 678 func (app *App) Use(args ...interface{}) Router { 679 var prefix string 680 var prefixes []string 681 var handlers []Handler 682 683 for i := 0; i < len(args); i++ { 684 switch arg := args[i].(type) { 685 case string: 686 prefix = arg 687 case []string: 688 prefixes = arg 689 case Handler: 690 handlers = append(handlers, arg) 691 default: 692 panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) 693 } 694 } 695 696 if len(prefixes) == 0 { 697 prefixes = append(prefixes, prefix) 698 } 699 700 for _, prefix := range prefixes { 701 app.register(methodUse, prefix, nil, handlers...) 702 } 703 704 return app 705 } 706 707 // Get registers a route for GET methods that requests a representation 708 // of the specified resource. Requests using GET should only retrieve data. 709 func (app *App) Get(path string, handlers ...Handler) Router { 710 return app.Head(path, handlers...).Add(MethodGet, path, handlers...) 711 } 712 713 // Head registers a route for HEAD methods that asks for a response identical 714 // to that of a GET request, but without the response body. 715 func (app *App) Head(path string, handlers ...Handler) Router { 716 return app.Add(MethodHead, path, handlers...) 717 } 718 719 // Post registers a route for POST methods that is used to submit an entity to the 720 // specified resource, often causing a change in state or side effects on the server. 721 func (app *App) Post(path string, handlers ...Handler) Router { 722 return app.Add(MethodPost, path, handlers...) 723 } 724 725 // Put registers a route for PUT methods that replaces all current representations 726 // of the target resource with the request payload. 727 func (app *App) Put(path string, handlers ...Handler) Router { 728 return app.Add(MethodPut, path, handlers...) 729 } 730 731 // Delete registers a route for DELETE methods that deletes the specified resource. 732 func (app *App) Delete(path string, handlers ...Handler) Router { 733 return app.Add(MethodDelete, path, handlers...) 734 } 735 736 // Connect registers a route for CONNECT methods that establishes a tunnel to the 737 // server identified by the target resource. 738 func (app *App) Connect(path string, handlers ...Handler) Router { 739 return app.Add(MethodConnect, path, handlers...) 740 } 741 742 // Options registers a route for OPTIONS methods that is used to describe the 743 // communication options for the target resource. 744 func (app *App) Options(path string, handlers ...Handler) Router { 745 return app.Add(MethodOptions, path, handlers...) 746 } 747 748 // Trace registers a route for TRACE methods that performs a message loop-back 749 // test along the path to the target resource. 750 func (app *App) Trace(path string, handlers ...Handler) Router { 751 return app.Add(MethodTrace, path, handlers...) 752 } 753 754 // Patch registers a route for PATCH methods that is used to apply partial 755 // modifications to a resource. 756 func (app *App) Patch(path string, handlers ...Handler) Router { 757 return app.Add(MethodPatch, path, handlers...) 758 } 759 760 // Add allows you to specify a HTTP method to register a route 761 func (app *App) Add(method, path string, handlers ...Handler) Router { 762 app.register(method, path, nil, handlers...) 763 764 return app 765 } 766 767 // Static will create a file server serving static files 768 func (app *App) Static(prefix, root string, config ...Static) Router { 769 app.registerStatic(prefix, root, config...) 770 771 return app 772 } 773 774 // All will register the handler on all HTTP methods 775 func (app *App) All(path string, handlers ...Handler) Router { 776 for _, method := range app.config.RequestMethods { 777 _ = app.Add(method, path, handlers...) 778 } 779 return app 780 } 781 782 // Group is used for Routes with common prefix to define a new sub-router with optional middleware. 783 // 784 // api := app.Group("/api") 785 // api.Get("/users", handler) 786 func (app *App) Group(prefix string, handlers ...Handler) Router { 787 grp := &Group{Prefix: prefix, app: app} 788 if len(handlers) > 0 { 789 app.register(methodUse, prefix, grp, handlers...) 790 } 791 if err := app.hooks.executeOnGroupHooks(*grp); err != nil { 792 panic(err) 793 } 794 795 return grp 796 } 797 798 // Route is used to define routes with a common prefix inside the common function. 799 // Uses Group method to define new sub-router. 800 func (app *App) Route(prefix string, fn func(router Router), name ...string) Router { 801 // Create new group 802 group := app.Group(prefix) 803 if len(name) > 0 { 804 group.Name(name[0]) 805 } 806 807 // Define routes 808 fn(group) 809 810 return group 811 } 812 813 // Error makes it compatible with the `error` interface. 814 func (e *Error) Error() string { 815 return e.Message 816 } 817 818 // NewError creates a new Error instance with an optional message 819 func NewError(code int, message ...string) *Error { 820 err := &Error{ 821 Code: code, 822 Message: utils.StatusMessage(code), 823 } 824 if len(message) > 0 { 825 err.Message = message[0] 826 } 827 return err 828 } 829 830 // Config returns the app config as value ( read-only ). 831 func (app *App) Config() Config { 832 return app.config 833 } 834 835 // Handler returns the server handler. 836 func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476 837 // prepare the server for the start 838 app.startupProcess() 839 return app.handler 840 } 841 842 // Stack returns the raw router stack. 843 func (app *App) Stack() [][]*Route { 844 return app.stack 845 } 846 847 // HandlersCount returns the amount of registered handlers. 848 func (app *App) HandlersCount() uint32 { 849 return app.handlersCount 850 } 851 852 // Shutdown gracefully shuts down the server without interrupting any active connections. 853 // Shutdown works by first closing all open listeners and then waiting indefinitely for all connections to return to idle before shutting down. 854 // 855 // Make sure the program doesn't exit and waits instead for Shutdown to return. 856 // 857 // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 858 func (app *App) Shutdown() error { 859 return app.ShutdownWithContext(context.Background()) 860 } 861 862 // ShutdownWithTimeout gracefully shuts down the server without interrupting any active connections. However, if the timeout is exceeded, 863 // ShutdownWithTimeout will forcefully close any active connections. 864 // ShutdownWithTimeout works by first closing all open listeners and then waiting for all connections to return to idle before shutting down. 865 // 866 // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. 867 // 868 // ShutdownWithTimeout does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 869 func (app *App) ShutdownWithTimeout(timeout time.Duration) error { 870 ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) 871 defer cancelFunc() 872 return app.ShutdownWithContext(ctx) 873 } 874 875 // ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. 876 // 877 // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. 878 // 879 // ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 880 func (app *App) ShutdownWithContext(ctx context.Context) error { 881 if app.hooks != nil { 882 defer app.hooks.executeOnShutdownHooks() 883 } 884 885 app.mutex.Lock() 886 defer app.mutex.Unlock() 887 if app.server == nil { 888 return fmt.Errorf("shutdown: server is not running") 889 } 890 return app.server.ShutdownWithContext(ctx) 891 } 892 893 // Server returns the underlying fasthttp server 894 func (app *App) Server() *fasthttp.Server { 895 return app.server 896 } 897 898 // Hooks returns the hook struct to register hooks. 899 func (app *App) Hooks() *Hooks { 900 return app.hooks 901 } 902 903 // Test is used for internal debugging by passing a *http.Request. 904 // Timeout is optional and defaults to 1s, -1 will disable it completely. 905 func (app *App) Test(req *http.Request, msTimeout ...int) (*http.Response, error) { 906 // Set timeout 907 timeout := 1000 908 if len(msTimeout) > 0 { 909 timeout = msTimeout[0] 910 } 911 912 // Add Content-Length if not provided with body 913 if req.Body != http.NoBody && req.Header.Get(HeaderContentLength) == "" { 914 req.Header.Add(HeaderContentLength, strconv.FormatInt(req.ContentLength, 10)) 915 } 916 917 // Dump raw http request 918 dump, err := httputil.DumpRequest(req, true) 919 if err != nil { 920 return nil, fmt.Errorf("failed to dump request: %w", err) 921 } 922 923 // Create test connection 924 conn := new(testConn) 925 926 // Write raw http request 927 if _, err := conn.r.Write(dump); err != nil { 928 return nil, fmt.Errorf("failed to write: %w", err) 929 } 930 // prepare the server for the start 931 app.startupProcess() 932 933 // Serve conn to server 934 channel := make(chan error) 935 go func() { 936 var returned bool 937 defer func() { 938 if !returned { 939 channel <- fmt.Errorf("runtime.Goexit() called in handler or server panic") 940 } 941 }() 942 943 channel <- app.server.ServeConn(conn) 944 returned = true 945 }() 946 947 // Wait for callback 948 if timeout >= 0 { 949 // With timeout 950 select { 951 case err = <-channel: 952 case <-time.After(time.Duration(timeout) * time.Millisecond): 953 return nil, fmt.Errorf("test: timeout error %vms", timeout) 954 } 955 } else { 956 // Without timeout 957 err = <-channel 958 } 959 960 // Check for errors 961 if err != nil && !errors.Is(err, fasthttp.ErrGetOnly) { 962 return nil, err 963 } 964 965 // Read response 966 buffer := bufio.NewReader(&conn.w) 967 968 // Convert raw http response to *http.Response 969 res, err := http.ReadResponse(buffer, req) 970 if err != nil { 971 return nil, fmt.Errorf("failed to read response: %w", err) 972 } 973 974 return res, nil 975 } 976 977 type disableLogger struct{} 978 979 func (*disableLogger) Printf(_ string, _ ...interface{}) { 980 // fmt.Println(fmt.Sprintf(format, args...)) 981 } 982 983 func (app *App) init() *App { 984 // lock application 985 app.mutex.Lock() 986 987 // Only load templates if a view engine is specified 988 if app.config.Views != nil { 989 if err := app.config.Views.Load(); err != nil { 990 log.Printf("[Warning]: failed to load views: %v\n", err) 991 } 992 } 993 994 // create fasthttp server 995 app.server = &fasthttp.Server{ 996 Logger: &disableLogger{}, 997 LogAllErrors: false, 998 ErrorHandler: app.serverErrorHandler, 999 } 1000 1001 // fasthttp server settings 1002 app.server.Handler = app.handler 1003 app.server.Name = app.config.ServerHeader 1004 app.server.Concurrency = app.config.Concurrency 1005 app.server.NoDefaultDate = app.config.DisableDefaultDate 1006 app.server.NoDefaultContentType = app.config.DisableDefaultContentType 1007 app.server.DisableHeaderNamesNormalizing = app.config.DisableHeaderNormalizing 1008 app.server.DisableKeepalive = app.config.DisableKeepalive 1009 app.server.MaxRequestBodySize = app.config.BodyLimit 1010 app.server.NoDefaultServerHeader = app.config.ServerHeader == "" 1011 app.server.ReadTimeout = app.config.ReadTimeout 1012 app.server.WriteTimeout = app.config.WriteTimeout 1013 app.server.IdleTimeout = app.config.IdleTimeout 1014 app.server.ReadBufferSize = app.config.ReadBufferSize 1015 app.server.WriteBufferSize = app.config.WriteBufferSize 1016 app.server.GetOnly = app.config.GETOnly 1017 app.server.ReduceMemoryUsage = app.config.ReduceMemoryUsage 1018 app.server.StreamRequestBody = app.config.StreamRequestBody 1019 app.server.DisablePreParseMultipartForm = app.config.DisablePreParseMultipartForm 1020 1021 // unlock application 1022 app.mutex.Unlock() 1023 return app 1024 } 1025 1026 // ErrorHandler is the application's method in charge of finding the 1027 // appropriate handler for the given request. It searches any mounted 1028 // sub fibers by their prefixes and if it finds a match, it uses that 1029 // error handler. Otherwise it uses the configured error handler for 1030 // the app, which if not set is the DefaultErrorHandler. 1031 func (app *App) ErrorHandler(ctx *Ctx, err error) error { 1032 var ( 1033 mountedErrHandler ErrorHandler 1034 mountedPrefixParts int 1035 ) 1036 1037 for prefix, subApp := range app.mountFields.appList { 1038 if prefix != "" && strings.HasPrefix(ctx.path, prefix) { 1039 parts := len(strings.Split(prefix, "/")) 1040 if mountedPrefixParts <= parts { 1041 if subApp.configured.ErrorHandler != nil { 1042 mountedErrHandler = subApp.config.ErrorHandler 1043 } 1044 1045 mountedPrefixParts = parts 1046 } 1047 } 1048 } 1049 1050 if mountedErrHandler != nil { 1051 return mountedErrHandler(ctx, err) 1052 } 1053 1054 return app.config.ErrorHandler(ctx, err) 1055 } 1056 1057 // serverErrorHandler is a wrapper around the application's error handler method 1058 // user for the fasthttp server configuration. It maps a set of fasthttp errors to fiber 1059 // errors before calling the application's error handler method. 1060 func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { 1061 c := app.AcquireCtx(fctx) 1062 defer app.ReleaseCtx(c) 1063 1064 var ( 1065 errNetOP *net.OpError 1066 netErr net.Error 1067 ) 1068 1069 switch { 1070 case errors.As(err, new(*fasthttp.ErrSmallBuffer)): 1071 err = ErrRequestHeaderFieldsTooLarge 1072 case errors.As(err, &errNetOP) && errNetOP.Timeout(): 1073 err = ErrRequestTimeout 1074 case errors.As(err, &netErr): 1075 err = ErrBadGateway 1076 case errors.Is(err, fasthttp.ErrBodyTooLarge): 1077 err = ErrRequestEntityTooLarge 1078 case errors.Is(err, fasthttp.ErrGetOnly): 1079 err = ErrMethodNotAllowed 1080 case strings.Contains(err.Error(), "timeout"): 1081 err = ErrRequestTimeout 1082 default: 1083 err = NewError(StatusBadRequest, err.Error()) 1084 } 1085 1086 if catch := app.ErrorHandler(c, err); catch != nil { 1087 log.Printf("serverErrorHandler: failed to call ErrorHandler: %v\n", catch) 1088 _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here 1089 return 1090 } 1091 } 1092 1093 // startupProcess Is the method which executes all the necessary processes just before the start of the server. 1094 func (app *App) startupProcess() *App { 1095 app.mutex.Lock() 1096 defer app.mutex.Unlock() 1097 1098 app.mountStartupProcess() 1099 1100 // build route tree stack 1101 app.buildTree() 1102 1103 return app 1104 } 1105 1106 // Run onListen hooks. If they return an error, panic. 1107 func (app *App) runOnListenHooks() { 1108 if err := app.hooks.executeOnListenHooks(); err != nil { 1109 panic(err) 1110 } 1111 }