github.com/boomhut/fiber/v2@v2.0.0-20230603160335-b65c856e57d3/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/boomhut/fiber/v2/utils" 28 29 "github.com/valyala/fasthttp" 30 ) 31 32 // Version of current fiber package 33 const Version = "2.46.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 Codename string `json:"codename"` 395 396 CodenameEmoji rune `json:"codename_emoji"` 397 } 398 399 // Static defines configuration options when defining static assets. 400 type Static struct { 401 // When set to true, the server tries minimizing CPU usage by caching compressed files. 402 // This works differently than the github.com/gofiber/compression middleware. 403 // Optional. Default value false 404 Compress bool `json:"compress"` 405 406 // When set to true, enables byte range requests. 407 // Optional. Default value false 408 ByteRange bool `json:"byte_range"` 409 410 // When set to true, enables directory browsing. 411 // Optional. Default value false. 412 Browse bool `json:"browse"` 413 414 // When set to true, enables direct download. 415 // Optional. Default value false. 416 Download bool `json:"download"` 417 418 // The name of the index file for serving a directory. 419 // Optional. Default value "index.html". 420 Index string `json:"index"` 421 422 // Expiration duration for inactive file handlers. 423 // Use a negative time.Duration to disable it. 424 // 425 // Optional. Default value 10 * time.Second. 426 CacheDuration time.Duration `json:"cache_duration"` 427 428 // The value for the Cache-Control HTTP-header 429 // that is set on the file response. MaxAge is defined in seconds. 430 // 431 // Optional. Default value 0. 432 MaxAge int `json:"max_age"` 433 434 // ModifyResponse defines a function that allows you to alter the response. 435 // 436 // Optional. Default: nil 437 ModifyResponse Handler 438 439 // Next defines a function to skip this middleware when returned true. 440 // 441 // Optional. Default: nil 442 Next func(c *Ctx) bool 443 } 444 445 // RouteMessage is some message need to be print when server starts 446 type RouteMessage struct { 447 name string 448 method string 449 path string 450 handlers string 451 } 452 453 // Default Config values 454 const ( 455 DefaultBodyLimit = 4 * 1024 * 1024 456 DefaultConcurrency = 256 * 1024 457 DefaultReadBufferSize = 4096 458 DefaultWriteBufferSize = 4096 459 DefaultCompressedFileSuffix = ".fiber.gz" 460 ) 461 462 // HTTP methods enabled by default 463 var DefaultMethods = []string{ 464 MethodGet, 465 MethodHead, 466 MethodPost, 467 MethodPut, 468 MethodDelete, 469 MethodConnect, 470 MethodOptions, 471 MethodTrace, 472 MethodPatch, 473 } 474 475 // DefaultErrorHandler that process return errors from handlers 476 func DefaultErrorHandler(c *Ctx, err error) error { 477 code := StatusInternalServerError 478 var e *Error 479 if errors.As(err, &e) { 480 code = e.Code 481 } 482 c.Set(HeaderContentType, MIMETextPlainCharsetUTF8) 483 return c.Status(code).SendString(err.Error()) 484 } 485 486 // New creates a new Fiber named instance. 487 // 488 // app := fiber.New() 489 // 490 // You can pass optional configuration options by passing a Config struct: 491 // 492 // app := fiber.New(fiber.Config{ 493 // Prefork: true, 494 // ServerHeader: "Fiber", 495 // }) 496 func New(config ...Config) *App { 497 // Create a new app 498 app := &App{ 499 // Create Ctx pool 500 pool: sync.Pool{ 501 New: func() interface{} { 502 return new(Ctx) 503 }, 504 }, 505 // Create config 506 config: Config{}, 507 getBytes: utils.UnsafeBytes, 508 getString: utils.UnsafeString, 509 latestRoute: &Route{}, 510 } 511 512 // Define hooks 513 app.hooks = newHooks(app) 514 515 // Define mountFields 516 app.mountFields = newMountFields(app) 517 518 // Override config if provided 519 if len(config) > 0 { 520 app.config = config[0] 521 } 522 523 // Initialize configured before defaults are set 524 app.configured = app.config 525 526 if app.config.ETag { 527 if !IsChild() { 528 log.Printf("[Warning] Config.ETag is deprecated since v2.0.6, please use 'middleware/etag'.\n") 529 } 530 } 531 532 // Override default values 533 if app.config.BodyLimit == 0 { 534 app.config.BodyLimit = DefaultBodyLimit 535 } 536 if app.config.Concurrency <= 0 { 537 app.config.Concurrency = DefaultConcurrency 538 } 539 if app.config.ReadBufferSize <= 0 { 540 app.config.ReadBufferSize = DefaultReadBufferSize 541 } 542 if app.config.WriteBufferSize <= 0 { 543 app.config.WriteBufferSize = DefaultWriteBufferSize 544 } 545 if app.config.CompressedFileSuffix == "" { 546 app.config.CompressedFileSuffix = DefaultCompressedFileSuffix 547 } 548 if app.config.Immutable { 549 app.getBytes, app.getString = getBytesImmutable, getStringImmutable 550 } 551 552 if app.config.ErrorHandler == nil { 553 app.config.ErrorHandler = DefaultErrorHandler 554 } 555 556 if app.config.JSONEncoder == nil { 557 app.config.JSONEncoder = json.Marshal 558 } 559 if app.config.JSONDecoder == nil { 560 app.config.JSONDecoder = json.Unmarshal 561 } 562 if app.config.XMLEncoder == nil { 563 app.config.XMLEncoder = xml.Marshal 564 } 565 if app.config.Network == "" { 566 app.config.Network = NetworkTCP4 567 } 568 if len(app.config.RequestMethods) == 0 { 569 app.config.RequestMethods = DefaultMethods 570 } 571 572 app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies)) 573 for _, ipAddress := range app.config.TrustedProxies { 574 app.handleTrustedProxy(ipAddress) 575 } 576 577 /// default Codename and Codename Emoji 578 if app.config.Codename == "" { 579 app.config.Codename = "Dodo" 580 } 581 582 if app.config.CodenameEmoji == 0 { 583 defEmojiString := "🐦" 584 // string to rune slice 585 emojiRune := []rune(defEmojiString) 586 app.config.CodenameEmoji = emojiRune[0] 587 588 } 589 590 // Create router stack 591 app.stack = make([][]*Route, len(app.config.RequestMethods)) 592 app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods)) 593 594 // Override colors 595 app.config.ColorScheme = defaultColors(app.config.ColorScheme) 596 597 // Init app 598 app.init() 599 600 // Return app 601 return app 602 } 603 604 // Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not 605 func (app *App) handleTrustedProxy(ipAddress string) { 606 if strings.Contains(ipAddress, "/") { 607 _, ipNet, err := net.ParseCIDR(ipAddress) 608 if err != nil { 609 log.Printf("[Warning] IP range %q could not be parsed: %v\n", ipAddress, err) 610 } else { 611 app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) 612 } 613 } else { 614 app.config.trustedProxiesMap[ipAddress] = struct{}{} 615 } 616 } 617 618 // SetTLSHandler You can use SetTLSHandler to use ClientHelloInfo when using TLS with Listener. 619 func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { 620 // Attach the tlsHandler to the config 621 app.mutex.Lock() 622 app.tlsHandler = tlsHandler 623 app.mutex.Unlock() 624 } 625 626 // Name Assign name to specific route. 627 func (app *App) Name(name string) Router { 628 app.mutex.Lock() 629 630 latestGroup := app.latestRoute.group 631 if latestGroup != nil { 632 app.latestRoute.Name = latestGroup.name + name 633 } else { 634 app.latestRoute.Name = name 635 } 636 637 if err := app.hooks.executeOnNameHooks(*app.latestRoute); err != nil { 638 panic(err) 639 } 640 app.mutex.Unlock() 641 642 return app 643 } 644 645 // GetRoute Get route by name 646 func (app *App) GetRoute(name string) Route { 647 for _, routes := range app.stack { 648 for _, route := range routes { 649 if route.Name == name { 650 return *route 651 } 652 } 653 } 654 655 return Route{} 656 } 657 658 // GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. 659 func (app *App) GetRoutes(filterUseOption ...bool) []Route { 660 var rs []Route 661 var filterUse bool 662 if len(filterUseOption) != 0 { 663 filterUse = filterUseOption[0] 664 } 665 for _, routes := range app.stack { 666 for _, route := range routes { 667 if filterUse && route.use { 668 continue 669 } 670 rs = append(rs, *route) 671 } 672 } 673 return rs 674 } 675 676 // Use registers a middleware route that will match requests 677 // with the provided prefix (which is optional and defaults to "/"). 678 // 679 // app.Use(func(c *fiber.Ctx) error { 680 // return c.Next() 681 // }) 682 // app.Use("/api", func(c *fiber.Ctx) error { 683 // return c.Next() 684 // }) 685 // app.Use("/api", handler, func(c *fiber.Ctx) error { 686 // return c.Next() 687 // }) 688 // 689 // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... 690 func (app *App) Use(args ...interface{}) Router { 691 var prefix string 692 var prefixes []string 693 var handlers []Handler 694 695 for i := 0; i < len(args); i++ { 696 switch arg := args[i].(type) { 697 case string: 698 prefix = arg 699 case []string: 700 prefixes = arg 701 case Handler: 702 handlers = append(handlers, arg) 703 default: 704 panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) 705 } 706 } 707 708 if len(prefixes) == 0 { 709 prefixes = append(prefixes, prefix) 710 } 711 712 for _, prefix := range prefixes { 713 app.register(methodUse, prefix, nil, handlers...) 714 } 715 716 return app 717 } 718 719 // Get registers a route for GET methods that requests a representation 720 // of the specified resource. Requests using GET should only retrieve data. 721 func (app *App) Get(path string, handlers ...Handler) Router { 722 return app.Head(path, handlers...).Add(MethodGet, path, handlers...) 723 } 724 725 // Head registers a route for HEAD methods that asks for a response identical 726 // to that of a GET request, but without the response body. 727 func (app *App) Head(path string, handlers ...Handler) Router { 728 return app.Add(MethodHead, path, handlers...) 729 } 730 731 // Post registers a route for POST methods that is used to submit an entity to the 732 // specified resource, often causing a change in state or side effects on the server. 733 func (app *App) Post(path string, handlers ...Handler) Router { 734 return app.Add(MethodPost, path, handlers...) 735 } 736 737 // Put registers a route for PUT methods that replaces all current representations 738 // of the target resource with the request payload. 739 func (app *App) Put(path string, handlers ...Handler) Router { 740 return app.Add(MethodPut, path, handlers...) 741 } 742 743 // Delete registers a route for DELETE methods that deletes the specified resource. 744 func (app *App) Delete(path string, handlers ...Handler) Router { 745 return app.Add(MethodDelete, path, handlers...) 746 } 747 748 // Connect registers a route for CONNECT methods that establishes a tunnel to the 749 // server identified by the target resource. 750 func (app *App) Connect(path string, handlers ...Handler) Router { 751 return app.Add(MethodConnect, path, handlers...) 752 } 753 754 // Options registers a route for OPTIONS methods that is used to describe the 755 // communication options for the target resource. 756 func (app *App) Options(path string, handlers ...Handler) Router { 757 return app.Add(MethodOptions, path, handlers...) 758 } 759 760 // Trace registers a route for TRACE methods that performs a message loop-back 761 // test along the path to the target resource. 762 func (app *App) Trace(path string, handlers ...Handler) Router { 763 return app.Add(MethodTrace, path, handlers...) 764 } 765 766 // Patch registers a route for PATCH methods that is used to apply partial 767 // modifications to a resource. 768 func (app *App) Patch(path string, handlers ...Handler) Router { 769 return app.Add(MethodPatch, path, handlers...) 770 } 771 772 // Add allows you to specify a HTTP method to register a route 773 func (app *App) Add(method, path string, handlers ...Handler) Router { 774 return app.register(method, path, nil, handlers...) 775 } 776 777 // Static will create a file server serving static files 778 func (app *App) Static(prefix, root string, config ...Static) Router { 779 return app.registerStatic(prefix, root, config...) 780 } 781 782 // All will register the handler on all HTTP methods 783 func (app *App) All(path string, handlers ...Handler) Router { 784 for _, method := range app.config.RequestMethods { 785 _ = app.Add(method, path, handlers...) 786 } 787 return app 788 } 789 790 // Group is used for Routes with common prefix to define a new sub-router with optional middleware. 791 // 792 // api := app.Group("/api") 793 // api.Get("/users", handler) 794 func (app *App) Group(prefix string, handlers ...Handler) Router { 795 grp := &Group{Prefix: prefix, app: app} 796 if len(handlers) > 0 { 797 app.register(methodUse, prefix, grp, handlers...) 798 } 799 if err := app.hooks.executeOnGroupHooks(*grp); err != nil { 800 panic(err) 801 } 802 803 return grp 804 } 805 806 // Route is used to define routes with a common prefix inside the common function. 807 // Uses Group method to define new sub-router. 808 func (app *App) Route(prefix string, fn func(router Router), name ...string) Router { 809 // Create new group 810 group := app.Group(prefix) 811 if len(name) > 0 { 812 group.Name(name[0]) 813 } 814 815 // Define routes 816 fn(group) 817 818 return group 819 } 820 821 // Error makes it compatible with the `error` interface. 822 func (e *Error) Error() string { 823 return e.Message 824 } 825 826 // NewError creates a new Error instance with an optional message 827 func NewError(code int, message ...string) *Error { 828 err := &Error{ 829 Code: code, 830 Message: utils.StatusMessage(code), 831 } 832 if len(message) > 0 { 833 err.Message = message[0] 834 } 835 return err 836 } 837 838 // Config returns the app config as value ( read-only ). 839 func (app *App) Config() Config { 840 return app.config 841 } 842 843 // Handler returns the server handler. 844 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 845 // prepare the server for the start 846 app.startupProcess() 847 return app.handler 848 } 849 850 // Stack returns the raw router stack. 851 func (app *App) Stack() [][]*Route { 852 return app.stack 853 } 854 855 // HandlersCount returns the amount of registered handlers. 856 func (app *App) HandlersCount() uint32 { 857 return app.handlersCount 858 } 859 860 // Shutdown gracefully shuts down the server without interrupting any active connections. 861 // Shutdown works by first closing all open listeners and then waiting indefinitely for all connections to return to idle before shutting down. 862 // 863 // Make sure the program doesn't exit and waits instead for Shutdown to return. 864 // 865 // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 866 func (app *App) Shutdown() error { 867 return app.ShutdownWithContext(context.Background()) 868 } 869 870 // ShutdownWithTimeout gracefully shuts down the server without interrupting any active connections. However, if the timeout is exceeded, 871 // ShutdownWithTimeout will forcefully close any active connections. 872 // ShutdownWithTimeout works by first closing all open listeners and then waiting for all connections to return to idle before shutting down. 873 // 874 // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. 875 // 876 // ShutdownWithTimeout does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 877 func (app *App) ShutdownWithTimeout(timeout time.Duration) error { 878 ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) 879 defer cancelFunc() 880 return app.ShutdownWithContext(ctx) 881 } 882 883 // ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. 884 // 885 // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. 886 // 887 // ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. 888 func (app *App) ShutdownWithContext(ctx context.Context) error { 889 if app.hooks != nil { 890 defer app.hooks.executeOnShutdownHooks() 891 } 892 893 app.mutex.Lock() 894 defer app.mutex.Unlock() 895 if app.server == nil { 896 return fmt.Errorf("shutdown: server is not running") 897 } 898 return app.server.ShutdownWithContext(ctx) 899 } 900 901 // Server returns the underlying fasthttp server 902 func (app *App) Server() *fasthttp.Server { 903 return app.server 904 } 905 906 // Hooks returns the hook struct to register hooks. 907 func (app *App) Hooks() *Hooks { 908 return app.hooks 909 } 910 911 // Test is used for internal debugging by passing a *http.Request. 912 // Timeout is optional and defaults to 1s, -1 will disable it completely. 913 func (app *App) Test(req *http.Request, msTimeout ...int) (*http.Response, error) { 914 // Set timeout 915 timeout := 1000 916 if len(msTimeout) > 0 { 917 timeout = msTimeout[0] 918 } 919 920 // Add Content-Length if not provided with body 921 if req.Body != http.NoBody && req.Header.Get(HeaderContentLength) == "" { 922 req.Header.Add(HeaderContentLength, strconv.FormatInt(req.ContentLength, 10)) 923 } 924 925 // Dump raw http request 926 dump, err := httputil.DumpRequest(req, true) 927 if err != nil { 928 return nil, fmt.Errorf("failed to dump request: %w", err) 929 } 930 931 // Create test connection 932 conn := new(testConn) 933 934 // Write raw http request 935 if _, err := conn.r.Write(dump); err != nil { 936 return nil, fmt.Errorf("failed to write: %w", err) 937 } 938 // prepare the server for the start 939 app.startupProcess() 940 941 // Serve conn to server 942 channel := make(chan error) 943 go func() { 944 var returned bool 945 defer func() { 946 if !returned { 947 channel <- fmt.Errorf("runtime.Goexit() called in handler or server panic") 948 } 949 }() 950 951 channel <- app.server.ServeConn(conn) 952 returned = true 953 }() 954 955 // Wait for callback 956 if timeout >= 0 { 957 // With timeout 958 select { 959 case err = <-channel: 960 case <-time.After(time.Duration(timeout) * time.Millisecond): 961 return nil, fmt.Errorf("test: timeout error %vms", timeout) 962 } 963 } else { 964 // Without timeout 965 err = <-channel 966 } 967 968 // Check for errors 969 if err != nil && !errors.Is(err, fasthttp.ErrGetOnly) { 970 return nil, err 971 } 972 973 // Read response 974 buffer := bufio.NewReader(&conn.w) 975 976 // Convert raw http response to *http.Response 977 res, err := http.ReadResponse(buffer, req) 978 if err != nil { 979 return nil, fmt.Errorf("failed to read response: %w", err) 980 } 981 982 return res, nil 983 } 984 985 type disableLogger struct{} 986 987 func (*disableLogger) Printf(_ string, _ ...interface{}) { 988 // fmt.Println(fmt.Sprintf(format, args...)) 989 } 990 991 func (app *App) init() *App { 992 // lock application 993 app.mutex.Lock() 994 995 // Only load templates if a view engine is specified 996 if app.config.Views != nil { 997 if err := app.config.Views.Load(); err != nil { 998 log.Printf("[Warning]: failed to load views: %v\n", err) 999 } 1000 } 1001 1002 // create fasthttp server 1003 app.server = &fasthttp.Server{ 1004 Logger: &disableLogger{}, 1005 LogAllErrors: false, 1006 ErrorHandler: app.serverErrorHandler, 1007 } 1008 1009 // fasthttp server settings 1010 app.server.Handler = app.handler 1011 app.server.Name = app.config.ServerHeader 1012 app.server.Concurrency = app.config.Concurrency 1013 app.server.NoDefaultDate = app.config.DisableDefaultDate 1014 app.server.NoDefaultContentType = app.config.DisableDefaultContentType 1015 app.server.DisableHeaderNamesNormalizing = app.config.DisableHeaderNormalizing 1016 app.server.DisableKeepalive = app.config.DisableKeepalive 1017 app.server.MaxRequestBodySize = app.config.BodyLimit 1018 app.server.NoDefaultServerHeader = app.config.ServerHeader == "" 1019 app.server.ReadTimeout = app.config.ReadTimeout 1020 app.server.WriteTimeout = app.config.WriteTimeout 1021 app.server.IdleTimeout = app.config.IdleTimeout 1022 app.server.ReadBufferSize = app.config.ReadBufferSize 1023 app.server.WriteBufferSize = app.config.WriteBufferSize 1024 app.server.GetOnly = app.config.GETOnly 1025 app.server.ReduceMemoryUsage = app.config.ReduceMemoryUsage 1026 app.server.StreamRequestBody = app.config.StreamRequestBody 1027 app.server.DisablePreParseMultipartForm = app.config.DisablePreParseMultipartForm 1028 1029 // unlock application 1030 app.mutex.Unlock() 1031 return app 1032 } 1033 1034 // ErrorHandler is the application's method in charge of finding the 1035 // appropriate handler for the given request. It searches any mounted 1036 // sub fibers by their prefixes and if it finds a match, it uses that 1037 // error handler. Otherwise it uses the configured error handler for 1038 // the app, which if not set is the DefaultErrorHandler. 1039 func (app *App) ErrorHandler(ctx *Ctx, err error) error { 1040 var ( 1041 mountedErrHandler ErrorHandler 1042 mountedPrefixParts int 1043 ) 1044 1045 for prefix, subApp := range app.mountFields.appList { 1046 if prefix != "" && strings.HasPrefix(ctx.path, prefix) { 1047 parts := len(strings.Split(prefix, "/")) 1048 if mountedPrefixParts <= parts { 1049 if subApp.configured.ErrorHandler != nil { 1050 mountedErrHandler = subApp.config.ErrorHandler 1051 } 1052 1053 mountedPrefixParts = parts 1054 } 1055 } 1056 } 1057 1058 if mountedErrHandler != nil { 1059 return mountedErrHandler(ctx, err) 1060 } 1061 1062 return app.config.ErrorHandler(ctx, err) 1063 } 1064 1065 // serverErrorHandler is a wrapper around the application's error handler method 1066 // user for the fasthttp server configuration. It maps a set of fasthttp errors to fiber 1067 // errors before calling the application's error handler method. 1068 func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { 1069 c := app.AcquireCtx(fctx) 1070 defer app.ReleaseCtx(c) 1071 1072 var ( 1073 errNetOP *net.OpError 1074 netErr net.Error 1075 ) 1076 1077 switch { 1078 case errors.As(err, new(*fasthttp.ErrSmallBuffer)): 1079 err = ErrRequestHeaderFieldsTooLarge 1080 case errors.As(err, &errNetOP) && errNetOP.Timeout(): 1081 err = ErrRequestTimeout 1082 case errors.As(err, &netErr): 1083 err = ErrBadGateway 1084 case errors.Is(err, fasthttp.ErrBodyTooLarge): 1085 err = ErrRequestEntityTooLarge 1086 case errors.Is(err, fasthttp.ErrGetOnly): 1087 err = ErrMethodNotAllowed 1088 case strings.Contains(err.Error(), "timeout"): 1089 err = ErrRequestTimeout 1090 default: 1091 err = NewError(StatusBadRequest, err.Error()) 1092 } 1093 1094 if catch := app.ErrorHandler(c, err); catch != nil { 1095 log.Printf("serverErrorHandler: failed to call ErrorHandler: %v\n", catch) 1096 _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here 1097 return 1098 } 1099 } 1100 1101 // startupProcess Is the method which executes all the necessary processes just before the start of the server. 1102 func (app *App) startupProcess() *App { 1103 if err := app.hooks.executeOnListenHooks(); err != nil { 1104 panic(err) 1105 } 1106 1107 app.mutex.Lock() 1108 defer app.mutex.Unlock() 1109 1110 app.mountStartupProcess() 1111 1112 // build route tree stack 1113 app.buildTree() 1114 1115 return app 1116 }