diff --git a/.github/README.md b/.github/README.md index 61159412e8..aa58a8d83c 100644 --- a/.github/README.md +++ b/.github/README.md @@ -39,7 +39,7 @@ Fiber v3 is currently in beta and under active development. While it offers exci ## βš™οΈ Installation -Fiber requires **Go version `1.22` or higher** to run. If you need to install or upgrade Go, visit the [official Go download page](https://go.dev/dl/). To start setting up your project. Create a new directory for your project and navigate into it. Then, initialize your project with Go modules by executing the following command in your terminal: +Fiber requires **Go version `1.22` or higher** to run. If you need to install or upgrade Go, visit the [official Go download page](https://go.dev/dl/). To start setting up your project, create a new directory for your project and navigate into it. Then, initialize your project with Go modules by executing the following command in your terminal: ```bash go mod init github.com/your/repo @@ -59,7 +59,7 @@ This command fetches the Fiber package and adds it to your project's dependencie Getting started with Fiber is easy. Here's a basic example to create a simple web server that responds with "Hello, World πŸ‘‹!" on the root path. This example demonstrates initializing a new Fiber app, setting up a route, and starting the server. -```go +```go title="Example" package main import ( @@ -133,7 +133,16 @@ Listed below are some of the common examples. If you want to see more code examp ### πŸ“– [**Basic Routing**](https://docs.gofiber.io/#basic-routing) -```go +```go title="Example" +package main + +import ( + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { app := fiber.New() @@ -169,23 +178,33 @@ func main() { log.Fatal(app.Listen(":3000")) } - ``` #### πŸ“– [**Route Naming**](https://docs.gofiber.io/api/app#name) -```go +```go title="Example" +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { app := fiber.New() - // GET /api/register app.Get("/api/*", func(c fiber.Ctx) error { msg := fmt.Sprintf("βœ‹ %s", c.Params("*")) return c.SendString(msg) // => βœ‹ register }).Name("api") - data, _ := json.MarshalIndent(app.GetRoute("api"), "", " ") - fmt.Print(string(data)) + route := app.GetRoute("api") + + data, _ := json.MarshalIndent(route, "", " ") + fmt.Println(string(data)) // Prints: // { // "method": "GET", @@ -198,15 +217,24 @@ func main() { log.Fatal(app.Listen(":3000")) } - ``` #### πŸ“– [**Serving Static Files**](https://docs.gofiber.io/api/app#static) -```go +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/static" +) + func main() { app := fiber.New() + // Serve static files from the "./public" directory app.Get("/*", static.New("./public")) // => http://localhost:3000/js/script.js // => http://localhost:3000/css/style.css @@ -215,27 +243,36 @@ func main() { // => http://localhost:3000/prefix/js/script.js // => http://localhost:3000/prefix/css/style.css + // Serve a single file for any unmatched routes app.Get("*", static.New("./public/index.html")) - // => http://localhost:3000/any/path/shows/index/html + // => http://localhost:3000/any/path/shows/index.html log.Fatal(app.Listen(":3000")) } - ``` #### πŸ“– [**Middleware & Next**](https://docs.gofiber.io/api/ctx#next) -```go +```go title="Example" +package main + +import ( + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { app := fiber.New() - // Match any route + // Middleware that matches any route app.Use(func(c fiber.Ctx) error { fmt.Println("πŸ₯‡ First handler") return c.Next() }) - // Match all routes starting with /api + // Middleware that matches all routes starting with /api app.Use("/api", func(c fiber.Ctx) error { fmt.Println("πŸ₯ˆ Second handler") return c.Next() @@ -249,13 +286,12 @@ func main() { log.Fatal(app.Listen(":3000")) } - ```
πŸ“š Show more code examples -### Views engines +### Views Engines πŸ“– [Config](https://docs.gofiber.io/api/fiber#config) πŸ“– [Engines](https://github.com/gofiber/template) @@ -263,11 +299,9 @@ func main() { Fiber defaults to the [html/template](https://pkg.go.dev/html/template/) when no view engine is set. -If you want to execute partials or use a different engine like [amber](https://github.com/eknkc/amber), [handlebars](https://github.com/aymerick/raymond), [mustache](https://github.com/cbroglie/mustache) or [pug](https://github.com/Joker/jade) etc.. - -Checkout our [Template](https://github.com/gofiber/template) package that support multiple view engines. +If you want to execute partials or use a different engine like [amber](https://github.com/eknkc/amber), [handlebars](https://github.com/aymerick/raymond), [mustache](https://github.com/cbroglie/mustache), or [pug](https://github.com/Joker/jade), etc., check out our [Template](https://github.com/gofiber/template) package that supports multiple view engines. -```go +```go title="Example" package main import ( @@ -278,12 +312,12 @@ import ( ) func main() { - // You can setup Views engine before initiation app: + // Initialize a new Fiber app with Pug template engine app := fiber.New(fiber.Config{ Views: pug.New("./views", ".pug"), }) - // And now, you can call template `./views/home.pug` like this: + // Define a route that renders the "home.pug" template app.Get("/", func(c fiber.Ctx) error { return c.Render("home", fiber.Map{ "title": "Homepage", @@ -295,24 +329,32 @@ func main() { } ``` -### Grouping routes into chains +### Grouping Routes into Chains πŸ“– [Group](https://docs.gofiber.io/api/app#group) -```go +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + func middleware(c fiber.Ctx) error { - fmt.Println("Don't mind me!") + log.Println("Middleware executed") return c.Next() } func handler(c fiber.Ctx) error { - return c.SendString(c.Path()) + return c.SendString("Handler response") } func main() { app := fiber.New() - // Root API route + // Root API group with middleware api := app.Group("/api", middleware) // /api // API v1 routes @@ -325,16 +367,15 @@ func main() { v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user - // ... + log.Fatal(app.Listen(":3000")) } - ``` -### Middleware logger +### Middleware Logger πŸ“– [Logger](https://docs.gofiber.io/api/middleware/logger) -```go +```go title="Example" package main import ( @@ -347,9 +388,13 @@ import ( func main() { app := fiber.New() + // Use Logger middleware app.Use(logger.New()) - // ... + // Define routes + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Hello, Logger!") + }) log.Fatal(app.Listen(":3000")) } @@ -359,7 +404,9 @@ func main() { πŸ“– [CORS](https://docs.gofiber.io/api/middleware/cors) -```go +```go title="Example" +package main + import ( "log" @@ -370,9 +417,13 @@ import ( func main() { app := fiber.New() + // Use CORS middleware with default settings app.Use(cors.New()) - // ... + // Define routes + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("CORS enabled!") + }) log.Fatal(app.Listen(":3000")) } @@ -384,28 +435,36 @@ Check CORS by passing any domain in `Origin` header: curl -H "Origin: http://example.com" --verbose http://localhost:3000 ``` -### Custom 404 response +### Custom 404 Response πŸ“– [HTTP Methods](https://docs.gofiber.io/api/ctx#status) -```go +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { app := fiber.New() + // Define routes app.Get("/", static.New("./public")) app.Get("/demo", func(c fiber.Ctx) error { - return c.SendString("This is a demo!") + return c.SendString("This is a demo page!") }) app.Post("/register", func(c fiber.Ctx) error { - return c.SendString("Welcome!") + return c.SendString("Registration successful!") }) - // Last middleware to match anything + // Middleware to handle 404 Not Found app.Use(func(c fiber.Ctx) error { - return c.SendStatus(404) - // => 404 "Not Found" + return c.SendStatus(fiber.StatusNotFound) // => 404 "Not Found" }) log.Fatal(app.Listen(":3000")) @@ -416,7 +475,15 @@ func main() { πŸ“– [JSON](https://docs.gofiber.io/api/ctx#json) -```go +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + type User struct { Name string `json:"name"` Age int `json:"age"` @@ -425,11 +492,13 @@ type User struct { func main() { app := fiber.New() + // Route that returns a JSON object app.Get("/user", func(c fiber.Ctx) error { return c.JSON(&User{"John", 20}) // => {"name":"John", "age":20} }) + // Route that returns a JSON map app.Get("/json", func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "success": true, @@ -446,7 +515,9 @@ func main() { πŸ“– [Websocket](https://github.com/gofiber/websocket) -```go +```go title="Example" +package main + import ( "log" @@ -455,26 +526,31 @@ import ( ) func main() { - app := fiber.New() - - app.Get("/ws", websocket.New(func(c *websocket.Conn) { - for { - mt, msg, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - break - } - log.Printf("recv: %s", msg) - err = c.WriteMessage(mt, msg) - if err != nil { - log.Println("write:", err) - break - } - } - })) - - log.Fatal(app.Listen(":3000")) - // ws://localhost:3000/ws + app := fiber.New() + + // WebSocket route + app.Get("/ws", websocket.New(func(c *websocket.Conn) { + defer c.Close() + for { + // Read message from client + mt, msg, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", msg) + + // Write message back to client + err = c.WriteMessage(mt, msg) + if err != nil { + log.Println("write:", err) + break + } + } + })) + + log.Fatal(app.Listen(":3000")) + // Connect via WebSocket at ws://localhost:3000/ws } ``` @@ -482,42 +558,46 @@ func main() { πŸ“– [More Info](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) -```go +```go title="Example" +package main + import ( + "bufio" + "fmt" "log" + "time" "github.com/gofiber/fiber/v3" "github.com/valyala/fasthttp" ) func main() { - app := fiber.New() - - app.Get("/sse", func(c fiber.Ctx) error { - c.Set("Content-Type", "text/event-stream") - c.Set("Cache-Control", "no-cache") - c.Set("Connection", "keep-alive") - c.Set("Transfer-Encoding", "chunked") - - c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { - fmt.Println("WRITER") - var i int - - for { - i++ - msg := fmt.Sprintf("%d - the time is %v", i, time.Now()) - fmt.Fprintf(w, "data: Message: %s\n\n", msg) - fmt.Println(msg) - - w.Flush() - time.Sleep(5 * time.Second) - } - })) + app := fiber.New() - return nil - }) + // Server-Sent Events route + app.Get("/sse", func(c fiber.Ctx) error { + c.Set("Content-Type", "text/event-stream") + c.Set("Cache-Control", "no-cache") + c.Set("Connection", "keep-alive") + c.Set("Transfer-Encoding", "chunked") + + c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { + var i int + for { + i++ + msg := fmt.Sprintf("%d - the time is %v", i, time.Now()) + fmt.Fprintf(w, "data: Message: %s\n\n", msg) + fmt.Println(msg) + + w.Flush() + time.Sleep(5 * time.Second) + } + }) - log.Fatal(app.Listen(":3000")) + return nil + }) + + log.Fatal(app.Listen(":3000")) } ``` @@ -525,7 +605,9 @@ func main() { πŸ“– [Recover](https://docs.gofiber.io/api/middleware/recover) -```go +```go title="Example" +package main + import ( "log" @@ -536,8 +618,10 @@ import ( func main() { app := fiber.New() + // Use Recover middleware to handle panics gracefully app.Use(recover.New()) + // Route that intentionally panics app.Get("/", func(c fiber.Ctx) error { panic("normally this would crash your app") }) @@ -546,13 +630,13 @@ func main() { } ``` -
- ### Using Trusted Proxy πŸ“– [Config](https://docs.gofiber.io/api/fiber#config) -```go +```go title="Example" +package main + import ( "log" @@ -561,13 +645,20 @@ import ( func main() { app := fiber.New(fiber.Config{ + // Configure trusted proxies - WARNING: Only trust proxies you control + // Using TrustProxy: true with unrestricted IPs can lead to IP spoofing TrustProxy: true, TrustProxyConfig: fiber.TrustProxyConfig{ - Proxies: []string{"0.0.0.0", "1.1.1.1/30"}, // IP address or IP address range + Proxies: []string{"10.0.0.0/8", "172.16.0.0/12"}, // Example: Internal network ranges only }, ProxyHeader: fiber.HeaderXForwardedFor, }) + // Define routes + app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Trusted Proxy Configured!") + }) + log.Fatal(app.Listen(":3000")) } ``` @@ -583,14 +674,14 @@ Here is a list of middleware that are included within the Fiber framework. | [adaptor](https://github.com/gofiber/fiber/tree/main/middleware/adaptor) | Converter for net/http handlers to/from Fiber request handlers. | | [basicauth](https://github.com/gofiber/fiber/tree/main/middleware/basicauth) | Provides HTTP basic authentication. It calls the next handler for valid credentials and 401 Unauthorized for missing or invalid credentials. | | [cache](https://github.com/gofiber/fiber/tree/main/middleware/cache) | Intercept and cache HTTP responses. | -| [compress](https://github.com/gofiber/fiber/tree/main/middleware/compress) | Compression middleware for Fiber, with support for `deflate`, `gzip`, `brotli` and `zstd`. | +| [compress](https://github.com/gofiber/fiber/tree/main/middleware/compress) | Compression middleware for Fiber, with support for `deflate`, `gzip`, `brotli` and `zstd`. | | [cors](https://github.com/gofiber/fiber/tree/main/middleware/cors) | Enable cross-origin resource sharing (CORS) with various options. | | [csrf](https://github.com/gofiber/fiber/tree/main/middleware/csrf) | Protect from CSRF exploits. | | [earlydata](https://github.com/gofiber/fiber/tree/main/middleware/earlydata) | Adds support for TLS 1.3's early data ("0-RTT") feature. | | [encryptcookie](https://github.com/gofiber/fiber/tree/main/middleware/encryptcookie) | Encrypt middleware which encrypts cookie values. | | [envvar](https://github.com/gofiber/fiber/tree/main/middleware/envvar) | Expose environment variables with providing an optional config. | | [etag](https://github.com/gofiber/fiber/tree/main/middleware/etag) | Allows for caches to be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. | -| [expvar](https://github.com/gofiber/fiber/tree/main/middleware/expvar) | Serves via its HTTP server runtime exposed variants in the JSON format. | +| [expvar](https://github.com/gofiber/fiber/tree/main/middleware/expvar) | Serves via its HTTP server runtime exposed variables in the JSON format. | | [favicon](https://github.com/gofiber/fiber/tree/main/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. | | [healthcheck](https://github.com/gofiber/fiber/tree/main/middleware/healthcheck) | Liveness and Readiness probes for Fiber. | | [helmet](https://github.com/gofiber/fiber/tree/main/middleware/helmet) | Helps secure your apps by setting various HTTP headers. | @@ -606,7 +697,7 @@ Here is a list of middleware that are included within the Fiber framework. | [rewrite](https://github.com/gofiber/fiber/tree/main/middleware/rewrite) | Rewrites the URL path based on provided rules. It can be helpful for backward compatibility or just creating cleaner and more descriptive links. | | [session](https://github.com/gofiber/fiber/tree/main/middleware/session) | Session middleware. NOTE: This middleware uses our Storage package. | | [skip](https://github.com/gofiber/fiber/tree/main/middleware/skip) | Skip middleware that skips a wrapped handler if a predicate is true. | -| [static](https://github.com/gofiber/fiber/tree/main/middleware/static) | Static middleware for Fiber that serves static files such as **images**, **CSS,** and **JavaScript**. | +| [static](https://github.com/gofiber/fiber/tree/main/middleware/static) | Static middleware for Fiber that serves static files such as **images**, **CSS**, and **JavaScript**. | | [timeout](https://github.com/gofiber/fiber/tree/main/middleware/timeout) | Adds a max time for a request and forwards to ErrorHandler if it is exceeded. | ## 🧬 External Middleware @@ -615,13 +706,13 @@ List of externally hosted middleware modules and maintained by the [Fiber team]( | Middleware | Description | | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | -| [contrib](https://github.com/gofiber/contrib) | Third party middlewares | -| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. | -| [template](https://github.com/gofiber/template) | This package contains 9 template engines that can be used with Fiber `v3` Go version 1.22 or higher is required. | +| [contrib](https://github.com/gofiber/contrib) | Third-party middlewares | +| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. | +| [template](https://github.com/gofiber/template) | This package contains 9 template engines that can be used with Fiber `v3`. Go version 1.22 or higher is required. | ## πŸ•ΆοΈ Awesome List -For more articles, middlewares, examples or tools check our [awesome list](https://github.com/gofiber/awesome-fiber). +For more articles, middlewares, examples, or tools, check our [awesome list](https://github.com/gofiber/awesome-fiber). ## πŸ‘ Contribute @@ -629,12 +720,12 @@ If you want to say **Thank You** and/or support the active development of `Fiber 1. Add a [GitHub Star](https://github.com/gofiber/fiber/stargazers) to the project. 2. Tweet about the project [on your 𝕏 (Twitter)](https://x.com/intent/tweet?text=Fiber%20is%20an%20Express%20inspired%20%23web%20%23framework%20built%20on%20top%20of%20Fasthttp%2C%20the%20fastest%20HTTP%20engine%20for%20%23Go.%20Designed%20to%20ease%20things%20up%20for%20%23fast%20development%20with%20zero%20memory%20allocation%20and%20%23performance%20in%20mind%20%F0%9F%9A%80%20https%3A%2F%2Fgithub.com%2Fgofiber%2Ffiber). -3. Write a review or tutorial on [Medium](https://medium.com/), [Dev.to](https://dev.to/) or personal blog. +3. Write a review or tutorial on [Medium](https://medium.com/), [Dev.to](https://dev.to/) or your personal blog. 4. Support the project by donating a [cup of coffee](https://buymeacoff.ee/fenny). -## πŸ–₯️ Development +## πŸ’» Development -To ensure your contributions are ready for a Pull Request, please use the following `Makefile` commands. These tools help maintain code quality, consistency. +To ensure your contributions are ready for a Pull Request, please use the following `Makefile` commands. These tools help maintain code quality and consistency. - **make help**: Display available commands. - **make audit**: Conduct quality checks. @@ -649,22 +740,22 @@ Run these commands to ensure your code adheres to project standards and best pra ## β˜• Supporters -Fiber is an open source project that runs on donations to pay the bills e.g. our domain name, gitbook, netlify and serverless hosting. If you want to support Fiber, you can β˜• [**buy a coffee here**](https://buymeacoff.ee/fenny). +Fiber is an open-source project that runs on donations to pay the bills, e.g., our domain name, GitBook, Netlify, and serverless hosting. If you want to support Fiber, you can β˜• [**buy a coffee here**](https://buymeacoff.ee/fenny). | | User | Donation | -| :--------------------------------------------------------- | :----------------------------------------------- | :------- | -| ![](https://avatars.githubusercontent.com/u/204341?s=25) | [@destari](https://github.com/destari) | β˜• x 10 | -| ![](https://avatars.githubusercontent.com/u/63164982?s=25) | [@dembygenesis](https://github.com/dembygenesis) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/56607882?s=25) | [@thomasvvugt](https://github.com/thomasvvugt) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/27820675?s=25) | [@hendratommy](https://github.com/hendratommy) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/1094221?s=25) | [@ekaputra07](https://github.com/ekaputra07) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/194590?s=25) | [@jorgefuertes](https://github.com/jorgefuertes) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/186637?s=25) | [@candidosales](https://github.com/candidosales) | β˜• x 5 | -| ![](https://avatars.githubusercontent.com/u/29659953?s=25) | [@l0nax](https://github.com/l0nax) | β˜• x 3 | -| ![](https://avatars.githubusercontent.com/u/635852?s=25) | [@bihe](https://github.com/bihe) | β˜• x 3 | -| ![](https://avatars.githubusercontent.com/u/307334?s=25) | [@justdave](https://github.com/justdave) | β˜• x 3 | -| ![](https://avatars.githubusercontent.com/u/11155743?s=25) | [@koddr](https://github.com/koddr) | β˜• x 1 | -| ![](https://avatars.githubusercontent.com/u/29042462?s=25) | [@lapolinar](https://github.com/lapolinar) | β˜• x 1 | +| ---------------------------------------------------------- | ------------------------------------------------ | -------- | +| ![](https://avatars.githubusercontent.com/u/204341?s=25) | [@destari](https://github.com/destari) | β˜• x 10 | +| ![](https://avatars.githubusercontent.com/u/63164982?s=25) | [@dembygenesis](https://github.com/dembygenesis) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/56607882?s=25) | [@thomasvvugt](https://github.com/thomasvvugt) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/27820675?s=25) | [@hendratommy](https://github.com/hendratommy) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/1094221?s=25) | [@ekaputra07](https://github.com/ekaputra07) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/194590?s=25) | [@jorgefuertes](https://github.com/jorgefuertes) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/186637?s=25) | [@candidosales](https://github.com/candidosales) | β˜• x 5 | +| ![](https://avatars.githubusercontent.com/u/29659953?s=25) | [@l0nax](https://github.com/l0nax) | β˜• x 3 | +| ![](https://avatars.githubusercontent.com/u/635852?s=25) | [@bihe](https://github.com/bihe) | β˜• x 3 | +| ![](https://avatars.githubusercontent.com/u/307334?s=25) | [@justdave](https://github.com/justdave) | β˜• x 3 | +| ![](https://avatars.githubusercontent.com/u/11155743?s=25) | [@koddr](https://github.com/koddr) | β˜• x 1 | +| ![](https://avatars.githubusercontent.com/u/29042462?s=25) | [@lapolinar](https://github.com/lapolinar) | β˜• x 1 | | ![](https://avatars.githubusercontent.com/u/2978730?s=25) | [@diegowifi](https://github.com/diegowifi) | β˜• x 1 | | ![](https://avatars.githubusercontent.com/u/44171355?s=25) | [@ssimk0](https://github.com/ssimk0) | β˜• x 1 | | ![](https://avatars.githubusercontent.com/u/5638101?s=25) | [@raymayemir](https://github.com/raymayemir) | β˜• x 1 | diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index d8d74905e4..a015149c22 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Run markdownlint-cli2 - uses: DavidAnson/markdownlint-cli2-action@v17 + uses: DavidAnson/markdownlint-cli2-action@v18 with: globs: | **/*.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ebf39c161c..e86be2956b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: - name: Upload coverage reports to Codecov if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }} - uses: codecov/codecov-action@v4.6.0 + uses: codecov/codecov-action@v5.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt diff --git a/bind.go b/bind.go index e202cd85e0..eff595cd72 100644 --- a/bind.go +++ b/bind.go @@ -95,7 +95,7 @@ func (b *Bind) RespHeader(out any) error { // Cookie binds the requesr cookie strings into the struct, map[string]string and map[string][]string. // NOTE: If your cookie is like key=val1,val2; they'll be binded as an slice if your map is map[string][]string. Else, it'll use last element of cookie. func (b *Bind) Cookie(out any) error { - if err := b.returnErr(binder.CookieBinder.Bind(b.ctx.Context(), out)); err != nil { + if err := b.returnErr(binder.CookieBinder.Bind(b.ctx.RequestCtx(), out)); err != nil { return err } @@ -104,7 +104,7 @@ func (b *Bind) Cookie(out any) error { // Query binds the query string into the struct, map[string]string and map[string][]string. func (b *Bind) Query(out any) error { - if err := b.returnErr(binder.QueryBinder.Bind(b.ctx.Context(), out)); err != nil { + if err := b.returnErr(binder.QueryBinder.Bind(b.ctx.RequestCtx(), out)); err != nil { return err } @@ -131,7 +131,7 @@ func (b *Bind) XML(out any) error { // Form binds the form into the struct, map[string]string and map[string][]string. func (b *Bind) Form(out any) error { - if err := b.returnErr(binder.FormBinder.Bind(b.ctx.Context(), out)); err != nil { + if err := b.returnErr(binder.FormBinder.Bind(b.ctx.RequestCtx(), out)); err != nil { return err } @@ -149,7 +149,7 @@ func (b *Bind) URI(out any) error { // MultipartForm binds the multipart form into the struct, map[string]string and map[string][]string. func (b *Bind) MultipartForm(out any) error { - if err := b.returnErr(binder.FormBinder.BindMultipart(b.ctx.Context(), out)); err != nil { + if err := b.returnErr(binder.FormBinder.BindMultipart(b.ctx.RequestCtx(), out)); err != nil { return err } @@ -163,7 +163,7 @@ func (b *Bind) MultipartForm(out any) error { // If there're no custom binder for mime type of body, it will return a ErrUnprocessableEntity error. func (b *Bind) Body(out any) error { // Get content-type - ctype := utils.ToLower(utils.UnsafeString(b.ctx.Context().Request.Header.ContentType())) + ctype := utils.ToLower(utils.UnsafeString(b.ctx.RequestCtx().Request.Header.ContentType())) ctype = binder.FilterFlags(utils.ParseVendorSpecificContentType(ctype)) // Check custom binders diff --git a/client/client_test.go b/client/client_test.go index b8dd39bbf0..0323f70ccd 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1572,7 +1572,7 @@ func Test_Client_SetProxyURL(t *testing.T) { } c.Status(resp.StatusCode()) - c.Context().SetBody(resp.Body()) + c.RequestCtx().SetBody(resp.Body()) return nil }) diff --git a/ctx.go b/ctx.go index fcf5c138da..a2eee2754b 100644 --- a/ctx.go +++ b/ctx.go @@ -382,26 +382,26 @@ func (c *DefaultCtx) ClearCookie(key ...string) { }) } -// Context returns *fasthttp.RequestCtx that carries a deadline +// RequestCtx returns *fasthttp.RequestCtx that carries a deadline // a cancellation signal, and other values across API boundaries. -func (c *DefaultCtx) Context() *fasthttp.RequestCtx { +func (c *DefaultCtx) RequestCtx() *fasthttp.RequestCtx { return c.fasthttp } -// UserContext returns a context implementation that was set by +// Context returns a context implementation that was set by // user earlier or returns a non-nil, empty context,if it was not set earlier. -func (c *DefaultCtx) UserContext() context.Context { +func (c *DefaultCtx) Context() context.Context { ctx, ok := c.fasthttp.UserValue(userContextKey).(context.Context) if !ok { ctx = context.Background() - c.SetUserContext(ctx) + c.SetContext(ctx) } return ctx } -// SetUserContext sets a context implementation by user. -func (c *DefaultCtx) SetUserContext(ctx context.Context) { +// SetContext sets a context implementation by user. +func (c *DefaultCtx) SetContext(ctx context.Context) { c.fasthttp.SetUserValue(userContextKey, ctx) } @@ -1189,8 +1189,8 @@ func (c *DefaultCtx) Query(key string, defaultValue ...string) string { // Queries()["filters[customer][name]"] == "Alice" // Queries()["filters[status]"] == "pending" func (c *DefaultCtx) Queries() map[string]string { - m := make(map[string]string, c.Context().QueryArgs().Len()) - c.Context().QueryArgs().VisitAll(func(key, value []byte) { + m := make(map[string]string, c.RequestCtx().QueryArgs().Len()) + c.RequestCtx().QueryArgs().VisitAll(func(key, value []byte) { m[c.app.getString(key)] = c.app.getString(value) }) return m @@ -1219,7 +1219,7 @@ func (c *DefaultCtx) Queries() map[string]string { // unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found func Query[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V - q := c.App().getString(c.Context().QueryArgs().Peek(key)) + q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key)) return genericParseType[V](q, v, defaultValue...) } @@ -1630,7 +1630,7 @@ func (c *DefaultCtx) SendFile(file string, config ...SendFile) error { // Apply cache control header if status != StatusNotFound && status != StatusForbidden { if len(cacheControlValue) > 0 { - c.Context().Response.Header.Set(HeaderCacheControl, cacheControlValue) + c.RequestCtx().Response.Header.Set(HeaderCacheControl, cacheControlValue) } return nil diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index 0714b24329..9fd434bc3e 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -45,14 +45,14 @@ type Ctx interface { // ClearCookie expires a specific cookie by key on the client side. // If no key is provided it expires all cookies that came with the request. ClearCookie(key ...string) - // Context returns *fasthttp.RequestCtx that carries a deadline + // RequestCtx returns *fasthttp.RequestCtx that carries a deadline // a cancellation signal, and other values across API boundaries. - Context() *fasthttp.RequestCtx - // UserContext returns a context implementation that was set by + RequestCtx() *fasthttp.RequestCtx + // Context returns a context implementation that was set by // user earlier or returns a non-nil, empty context,if it was not set earlier. - UserContext() context.Context - // SetUserContext sets a context implementation by user. - SetUserContext(ctx context.Context) + Context() context.Context + // SetContext sets a context implementation by user. + SetContext(ctx context.Context) // Cookie sets a cookie by passing a cookie struct. Cookie(cookie *Cookie) // Cookies are used for getting a cookie value by key. diff --git a/ctx_test.go b/ctx_test.go index 12ac115310..6254f5dfdf 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -843,24 +843,24 @@ func Benchmark_Ctx_Body_With_Compression_Immutable(b *testing.B) { } } -// go test -run Test_Ctx_Context -func Test_Ctx_Context(t *testing.T) { +// go test -run Test_Ctx_RequestCtx +func Test_Ctx_RequestCtx(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - require.Equal(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.Context())) + require.Equal(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.RequestCtx())) } -// go test -run Test_Ctx_UserContext -func Test_Ctx_UserContext(t *testing.T) { +// go test -run Test_Ctx_Context +func Test_Ctx_Context(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) t.Run("Nil_Context", func(t *testing.T) { t.Parallel() - ctx := c.UserContext() + ctx := c.Context() require.Equal(t, ctx, context.Background()) }) t.Run("ValueContext", func(t *testing.T) { @@ -872,8 +872,8 @@ func Test_Ctx_UserContext(t *testing.T) { }) } -// go test -run Test_Ctx_SetUserContext -func Test_Ctx_SetUserContext(t *testing.T) { +// go test -run Test_Ctx_SetContext +func Test_Ctx_SetContext(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -881,19 +881,19 @@ func Test_Ctx_SetUserContext(t *testing.T) { testKey := struct{}{} testValue := "Test Value" ctx := context.WithValue(context.Background(), testKey, testValue) //nolint: staticcheck // not needed for tests - c.SetUserContext(ctx) - require.Equal(t, testValue, c.UserContext().Value(testKey)) + c.SetContext(ctx) + require.Equal(t, testValue, c.Context().Value(testKey)) } -// go test -run Test_Ctx_UserContext_Multiple_Requests -func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) { +// go test -run Test_Ctx_Context_Multiple_Requests +func Test_Ctx_Context_Multiple_Requests(t *testing.T) { t.Parallel() testKey := struct{}{} testValue := "foobar-value" app := New() app.Get("/", func(c Ctx) error { - ctx := c.UserContext() + ctx := c.Context() if ctx.Value(testKey) != nil { return c.SendStatus(StatusInternalServerError) @@ -901,7 +901,7 @@ func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) { input := utils.CopyString(Query(c, "input", "NO_VALUE")) ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input)) //nolint: staticcheck // not needed for tests - c.SetUserContext(ctx) + c.SetContext(ctx) return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input)) }) @@ -913,7 +913,7 @@ func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) { resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil)) require.NoError(t, err, "Unexpected error from response") - require.Equal(t, StatusOK, resp.StatusCode, "context.Context returned from c.UserContext() is reused") + require.Equal(t, StatusOK, resp.StatusCode, "context.Context returned from c.Context() is reused") b, err := io.ReadAll(resp.Body) require.NoError(t, err, "Unexpected error from reading response body") @@ -3220,7 +3220,7 @@ func Test_Ctx_SendFile_MaxAge(t *testing.T) { // check expectation require.NoError(t, err) require.Equal(t, expectFileContent, c.Response().Body()) - require.Equal(t, "public, max-age=100", string(c.Context().Response.Header.Peek(HeaderCacheControl)), "CacheControl Control") + require.Equal(t, "public, max-age=100", string(c.RequestCtx().Response.Header.Peek(HeaderCacheControl)), "CacheControl Control") require.Equal(t, StatusOK, c.Response().StatusCode()) app.ReleaseCtx(c) } diff --git a/docs/api/app.md b/docs/api/app.md index 822d997988..a13b83c4bd 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -17,18 +17,28 @@ import RoutingHandler from './../partials/routing/handler.md'; ### Mounting -You can Mount Fiber instance using the [`app.Use`](./app.md#use) method similar to [`express`](https://expressjs.com/en/api.html#router.use). +You can mount a Fiber instance using the [`app.Use`](./app.md#use) method, similar to [`Express`](https://expressjs.com/en/api.html#router.use). + +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) -```go title="Examples" func main() { app := fiber.New() micro := fiber.New() + + // Mount the micro app on the "/john" route app.Use("/john", micro) // GET /john/doe -> 200 OK - + micro.Get("/doe", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) - + log.Fatal(app.Listen(":3000")) } ``` @@ -41,7 +51,15 @@ The `MountPath` property contains one or more path patterns on which a sub-app w func (app *App) MountPath() string ``` -```go title="Examples" +```go title="Example" +package main + +import ( + "fmt" + + "github.com/gofiber/fiber/v3" +) + func main() { app := fiber.New() one := fiber.New() @@ -51,16 +69,17 @@ func main() { two.Use("/three", three) one.Use("/two", two) app.Use("/one", one) - - one.MountPath() // "/one" - two.MountPath() // "/one/two" - three.MountPath() // "/one/two/three" - app.MountPath() // "" + + fmt.Println("Mount paths:") + fmt.Println("one.MountPath():", one.MountPath()) // "/one" + fmt.Println("two.MountPath():", two.MountPath()) // "/one/two" + fmt.Println("three.MountPath():", three.MountPath()) // "/one/two/three" + fmt.Println("app.MountPath():", app.MountPath()) // "" } ``` :::caution -Mounting order is important for MountPath. If you want to get mount paths properly, you should start mounting from the deepest app. +Mounting order is important for `MountPath`. To get mount paths properly, you should start mounting from the deepest app. ::: ### Group @@ -71,21 +90,33 @@ You can group routes by creating a `*Group` struct. func (app *App) Group(prefix string, handlers ...Handler) Router ``` -```go title="Examples" +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { - app := fiber.New() + app := fiber.New() + + api := app.Group("/api", handler) // /api - api := app.Group("/api", handler) // /api + v1 := api.Group("/v1", handler) // /api/v1 + v1.Get("/list", handler) // /api/v1/list + v1.Get("/user", handler) // /api/v1/user - v1 := api.Group("/v1", handler) // /api/v1 - v1.Get("/list", handler) // /api/v1/list - v1.Get("/user", handler) // /api/v1/user + v2 := api.Group("/v2", handler) // /api/v2 + v2.Get("/list", handler) // /api/v2/list + v2.Get("/user", handler) // /api/v2/user - v2 := api.Group("/v2", handler) // /api/v2 - v2.Get("/list", handler) // /api/v2/list - v2.Get("/user", handler) // /api/v2/user + log.Fatal(app.Listen(":3000")) +} - log.Fatal(app.Listen(":3000")) +func handler(c fiber.Ctx) error { + return c.SendString("Handler response") } ``` @@ -104,64 +135,73 @@ func (app *App) Route(path string) Register ```go type Register interface { - All(handler Handler, middleware ...Handler) Register - Get(handler Handler, middleware ...Handler) Register - Head(handler Handler, middleware ...Handler) Register - Post(handler Handler, middleware ...Handler) Register - Put(handler Handler, middleware ...Handler) Register - Delete(handler Handler, middleware ...Handler) Register - Connect(handler Handler, middleware ...Handler) Register - Options(handler Handler, middleware ...Handler) Register - Trace(handler Handler, middleware ...Handler) Register - Patch(handler Handler, middleware ...Handler) Register - - Add(methods []string, handler Handler, middleware ...Handler) Register - - Route(path string) Register + All(handler Handler, middleware ...Handler) Register + Get(handler Handler, middleware ...Handler) Register + Head(handler Handler, middleware ...Handler) Register + Post(handler Handler, middleware ...Handler) Register + Put(handler Handler, middleware ...Handler) Register + Delete(handler Handler, middleware ...Handler) Register + Connect(handler Handler, middleware ...Handler) Register + Options(handler Handler, middleware ...Handler) Register + Trace(handler Handler, middleware ...Handler) Register + Patch(handler Handler, middleware ...Handler) Register + + Add(methods []string, handler Handler, middleware ...Handler) Register + + Route(path string) Register } ``` -```go title="Examples" +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + func main() { - app := fiber.New() - - // use `Route` as chainable route declaration method - app.Route("/test").Get(func(c fiber.Ctx) error { - return c.SendString("GET /test") - }) - - app.Route("/events").all(func(c fiber.Ctx) error { - // runs for all HTTP verbs first - // think of it as route specific middleware! - }) - .get(func(c fiber.Ctx) error { - return c.SendString("GET /events") - }) - .post(func(c fiber.Ctx) error { - // maybe add a new event... - }) - - // combine multiple routes - app.Route("/v2").Route("/user").Get(func(c fiber.Ctx) error { - return c.SendString("GET /v2/user") - }) - - // use multiple methods - app.Route("/api").Get(func(c fiber.Ctx) error { - return c.SendString("GET /api") - }).Post(func(c fiber.Ctx) error { - return c.SendString("POST /api") - }) - - log.Fatal(app.Listen(":3000")) + app := fiber.New() + + // Use `Route` as a chainable route declaration method + app.Route("/test").Get(func(c fiber.Ctx) error { + return c.SendString("GET /test") + }) + + app.Route("/events").All(func(c fiber.Ctx) error { + // Runs for all HTTP verbs first + // Think of it as route-specific middleware! + }). + Get(func(c fiber.Ctx) error { + return c.SendString("GET /events") + }). + Post(func(c fiber.Ctx) error { + // Maybe add a new event... + return c.SendString("POST /events") + }) + + // Combine multiple routes + app.Route("/v2").Route("/user").Get(func(c fiber.Ctx) error { + return c.SendString("GET /v2/user") + }) + + // Use multiple methods + app.Route("/api").Get(func(c fiber.Ctx) error { + return c.SendString("GET /api") + }).Post(func(c fiber.Ctx) error { + return c.SendString("POST /api") + }) + + log.Fatal(app.Listen(":3000")) } ``` ### HandlersCount -This method returns the amount of registered handlers. +This method returns the number of registered handlers. ```go title="Signature" func (app *App) HandlersCount() uint32 @@ -169,13 +209,22 @@ func (app *App) HandlersCount() uint32 ### Stack -This method returns the original router stack +This method returns the original router stack. ```go title="Signature" func (app *App) Stack() [][]*Route ``` -```go title="Examples" +```go title="Example" +package main + +import ( + "encoding/json" + "log" + + "github.com/gofiber/fiber/v3" +) + var handler = func(c fiber.Ctx) error { return nil } func main() { @@ -187,7 +236,7 @@ func main() { data, _ := json.MarshalIndent(app.Stack(), "", " ") fmt.Println(string(data)) - app.Listen(":3000") + log.Fatal(app.Listen(":3000")) } ``` @@ -228,25 +277,32 @@ func main() { ### Name -This method assigns the name of latest created route. +This method assigns the name to the latest created route. ```go title="Signature" func (app *App) Name(name string) Router ``` -```go title="Examples" -var handler = func(c fiber.Ctx) error { return nil } +```go title="Example" +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) func main() { + var handler = func(c fiber.Ctx) error { return nil } + app := fiber.New() app.Get("/", handler) app.Name("index") - app.Get("/doe", handler).Name("home") - app.Trace("/tracer", handler).Name("tracert") - app.Delete("/delete", handler).Name("delete") a := app.Group("/a") @@ -255,10 +311,9 @@ func main() { a.Get("/test", handler).Name("test") data, _ := json.MarshalIndent(app.Stack(), "", " ") - fmt.Print(string(data)) - - app.Listen(":3000") + fmt.Println(string(data)) + log.Fatal(app.Listen(":3000")) } ``` @@ -335,25 +390,34 @@ func main() { ### GetRoute -This method gets the route by name. +This method retrieves a route by its name. ```go title="Signature" func (app *App) GetRoute(name string) Route ``` -```go title="Examples" -var handler = func(c fiber.Ctx) error { return nil } +```go title="Example" +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) func main() { app := fiber.New() app.Get("/", handler).Name("index") - data, _ := json.MarshalIndent(app.GetRoute("index"), "", " ") - fmt.Print(string(data)) + route := app.GetRoute("index") + data, _ := json.MarshalIndent(route, "", " ") + fmt.Println(string(data)) - app.Listen(":3000") + log.Fatal(app.Listen(":3000")) } ``` @@ -373,22 +437,38 @@ func main() { ### GetRoutes -This method gets all routes. +This method retrieves all routes. ```go title="Signature" func (app *App) GetRoutes(filterUseOption ...bool) []Route ``` -When filterUseOption equal to true, it will filter the routes registered by the middleware. +When `filterUseOption` is set to `true`, it filters out routes registered by middleware. + +```go title="Example" +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/gofiber/fiber/v3" +) -```go title="Examples" func main() { app := fiber.New() - app.Post("/", func (c fiber.Ctx) error { + + app.Post("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }).Name("index") - data, _ := json.MarshalIndent(app.GetRoutes(true), "", " ") - fmt.Print(string(data)) + + routes := app.GetRoutes(true) + + data, _ := json.MarshalIndent(routes, "", " ") + fmt.Println(string(data)) + + log.Fatal(app.Listen(":3000")) } ``` @@ -410,7 +490,7 @@ func main() { ## Config -Config returns the [app config](./fiber.md#config) as value \( read-only \). +`Config` returns the [app config](./fiber.md#config) as a value (read-only). ```go title="Signature" func (app *App) Config() Config @@ -418,7 +498,7 @@ func (app *App) Config() Config ## Handler -Handler returns the server handler that can be used to serve custom [`\*fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) requests. +`Handler` returns the server handler that can be used to serve custom [`\*fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) requests. ```go title="Signature" func (app *App) Handler() fasthttp.RequestHandler @@ -426,7 +506,7 @@ func (app *App) Handler() fasthttp.RequestHandler ## ErrorHandler -Errorhandler executes the process which was defined for the application in case of errors, this is used in some cases in middlewares. +`ErrorHandler` executes the process defined for the application in case of errors. This is used in some cases in middlewares. ```go title="Signature" func (app *App) ErrorHandler(ctx Ctx, err error) error @@ -434,15 +514,23 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error ## NewCtxFunc -NewCtxFunc allows to customize the ctx struct as we want. +`NewCtxFunc` allows you to customize the `ctx` struct as needed. ```go title="Signature" func (app *App) NewCtxFunc(function func(app *App) CustomCtx) ``` -```go title="Examples" +```go title="Example" +package main + +import ( + "fmt" + + "github.com/gofiber/fiber/v3" +) + type CustomCtx struct { - DefaultCtx + fiber.DefaultCtx } // Custom method @@ -450,86 +538,102 @@ func (c *CustomCtx) Params(key string, defaultValue ...string) string { return "prefix_" + c.DefaultCtx.Params(key) } -app := New() -app.NewCtxFunc(func(app *fiber.App) fiber.CustomCtx { - return &CustomCtx{ - DefaultCtx: *NewDefaultCtx(app), - } -}) -// curl http://localhost:3000/123 -app.Get("/:id", func(c Ctx) error { - // use custom method - output: prefix_123 - return c.SendString(c.Params("id")) -}) +func main() { + app := fiber.New() + + app.NewCtxFunc(func(app *fiber.App) fiber.CustomCtx { + return &CustomCtx{ + DefaultCtx: *fiber.NewDefaultCtx(app), + } + }) + + app.Get("/:id", func(c fiber.Ctx) error { + // Use custom method - output: prefix_123 + return c.SendString(c.Params("id")) + }) + + log.Fatal(app.Listen(":3000")) +} ``` ## RegisterCustomBinder -You can register custom binders to use as [`Bind().Custom("name")`](bind.md#custom). -They should be compatible with CustomBinder interface. +You can register custom binders to use with [`Bind().Custom("name")`](bind.md#custom). They should be compatible with the `CustomBinder` interface. ```go title="Signature" func (app *App) RegisterCustomBinder(binder CustomBinder) ``` -```go title="Examples" -app := fiber.New() +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" + "gopkg.in/yaml.v2" +) + +type User struct { + Name string `yaml:"name"` +} + +type customBinder struct{} -// My custom binder -customBinder := &customBinder{} -// Name of custom binder, which will be used as Bind().Custom("name") func (*customBinder) Name() string { return "custom" } -// Is used in the Body Bind method to check if the binder should be used for custom mime types + func (*customBinder) MIMETypes() []string { return []string{"application/yaml"} } -// Parse the body and bind it to the out interface -func (*customBinder) Parse(c Ctx, out any) error { - // parse yaml body + +func (*customBinder) Parse(c fiber.Ctx, out any) error { + // Parse YAML body return yaml.Unmarshal(c.Body(), out) } -// Register custom binder -app.RegisterCustomBinder(customBinder) - -// curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John" -app.Post("/custom", func(c Ctx) error { - var user User - // output: {Name:John} - // Custom binder is used by the name - if err := c.Bind().Custom("custom", &user); err != nil { - return err - } - // ... - return c.JSON(user) -}) -// curl -X POST http://localhost:3000/normal -H "Content-Type: application/yaml" -d "name: Doe" -app.Post("/normal", func(c Ctx) error { - var user User - // output: {Name:Doe} - // Custom binder is used by the mime type - if err := c.Bind().Body(&user); err != nil { - return err - } - // ... - return c.JSON(user) -}) + +func main() { + app := fiber.New() + + // Register custom binder + app.RegisterCustomBinder(&customBinder{}) + + app.Post("/custom", func(c fiber.Ctx) error { + var user User + // Use Custom binder by name + if err := c.Bind().Custom("custom", &user); err != nil { + return err + } + return c.JSON(user) + }) + + app.Post("/normal", func(c fiber.Ctx) error { + var user User + // Custom binder is used by the MIME type + if err := c.Bind().Body(&user); err != nil { + return err + } + return c.JSON(user) + }) + + log.Fatal(app.Listen(":3000")) +} ``` ## RegisterCustomConstraint -RegisterCustomConstraint allows to register custom constraint. +`RegisterCustomConstraint` allows you to register custom constraints. ```go title="Signature" func (app *App) RegisterCustomConstraint(constraint CustomConstraint) ``` -See [Custom Constraint](../guide/routing.md#custom-constraint) section for more information. +See the [Custom Constraint](../guide/routing.md#custom-constraint) section for more information. ## SetTLSHandler -Use SetTLSHandler to set [ClientHelloInfo](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2) when using TLS with Listener. +Use `SetTLSHandler` to set [`ClientHelloInfo`](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2) when using TLS with a `Listener`. ```go title="Signature" func (app *App) SetTLSHandler(tlsHandler *TLSHandler) @@ -537,32 +641,47 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) ## Test -Testing your application is done with the **Test** method. Use this method for creating `_test.go` files or when you need to debug your routing logic. The default timeout is `1s`. If you want to disable a timeout altogether, pass a `TestConfig` struct with `Timeout: -1`. +Testing your application is done with the `Test` method. Use this method for creating `_test.go` files or when you need to debug your routing logic. The default timeout is `1s`; to disable a timeout altogether, pass a `TestConfig` struct with `Timeout: -1`. ```go title="Signature" func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) ``` -```go title="Examples" -// Create route with GET method for test: -app.Get("/", func(c fiber.Ctx) error { - fmt.Println(c.BaseURL()) // => http://google.com - fmt.Println(c.Get("X-Custom-Header")) // => hi +```go title="Example" +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "net/http/httptest" - return c.SendString("hello, World!") -}) + "github.com/gofiber/fiber/v3" +) + +func main() { + app := fiber.New() + + // Create route with GET method for test: + app.Get("/", func(c fiber.Ctx) error { + fmt.Println(c.BaseURL()) // => http://google.com + fmt.Println(c.Get("X-Custom-Header")) // => hi + return c.SendString("hello, World!") + }) -// http.Request -req := httptest.NewRequest("GET", "http://google.com", nil) -req.Header.Set("X-Custom-Header", "hi") + // Create http.Request + req := httptest.NewRequest("GET", "http://google.com", nil) + req.Header.Set("X-Custom-Header", "hi") -// http.Response -resp, _ := app.Test(req) + // Perform the test + resp, _ := app.Test(req) -// Do something with results: -if resp.StatusCode == fiber.StatusOK { - body, _ := io.ReadAll(resp.Body) - fmt.Println(string(body)) // => Hello, World! + // Do something with the results: + if resp.StatusCode == fiber.StatusOK { + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) // => hello, World! + } } ``` @@ -594,7 +713,7 @@ which would always result in a "test: empty response" error. ## Hooks -Hooks is a method to return [hooks](./hooks.md) property. +`Hooks` is a method to return the [hooks](./hooks.md) property. ```go title="Signature" func (app *App) Hooks() *Hooks @@ -602,7 +721,7 @@ func (app *App) Hooks() *Hooks ## RebuildTree -The RebuildTree method is designed to rebuild the route tree and enable dynamic route registration. It returns a pointer to the App instance. +The `RebuildTree` method is designed to rebuild the route tree and enable dynamic route registration. It returns a pointer to the `App` instance. ```go title="Signature" func (app *App) RebuildTree() *App @@ -614,16 +733,32 @@ func (app *App) RebuildTree() *App Here’s an example of how to define and register routes dynamically: -```go -app.Get("/define", func(c Ctx) error { // Define a new route dynamically - app.Get("/dynamically-defined", func(c Ctx) error { // Adding a dynamically defined route - return c.SendStatus(http.StatusOK) - }) +```go title="Example" +package main + +import ( + "log" + + "github.com/gofiber/fiber/v3" +) + +func main() { + app := fiber.New() + + app.Get("/define", func(c fiber.Ctx) error { + // Define a new route dynamically + app.Get("/dynamically-defined", func(c fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) - app.RebuildTree() // Rebuild the route tree to register the new route + // Rebuild the route tree to register the new route + app.RebuildTree() - return c.SendStatus(http.StatusOK) -}) + return c.SendStatus(fiber.StatusOK) + }) + + log.Fatal(app.Listen(":3000")) +} ``` -In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available. +In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available. diff --git a/docs/api/bind.md b/docs/api/bind.md index 73256cbbb8..2ad0854ca9 100644 --- a/docs/api/bind.md +++ b/docs/api/bind.md @@ -6,13 +6,11 @@ sidebar_position: 4 toc_max_heading_level: 4 --- -Bindings are used to parse the request/response body, query parameters, cookies and much more into a struct. +Bindings are used to parse the request/response body, query parameters, cookies, and much more into a struct. :::info - -All binder returned value are only valid within the handler. Do not store any references. +All binder returned values are only valid within the handler. Do not store any references. Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) - ::: ## Binders @@ -32,22 +30,21 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. Binds the request body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of `json:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called `Pass`, you would use a struct field with `json:"pass"`. -| content-type | struct tag | +| Content-Type | Struct Tag | | ----------------------------------- | ---------- | -| `application/x-www-form-urlencoded` | form | -| `multipart/form-data` | form | -| `application/json` | json | -| `application/xml` | xml | -| `text/xml` | xml | +| `application/x-www-form-urlencoded` | `form` | +| `multipart/form-data` | `form` | +| `application/json` | `json` | +| `application/xml` | `xml` | +| `text/xml` | `xml` | ```go title="Signature" func (b *Bind) Body(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { Name string `json:"name" xml:"name" form:"name"` Pass string `json:"pass" xml:"pass" form:"pass"` @@ -65,34 +62,35 @@ app.Post("/", func(c fiber.Ctx) error { // ... }) +``` -// Run tests with the following curl commands - -// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 +Run tests with the following `curl` commands: -// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 +```bash +# JSON +curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 -// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 +# XML +curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 -// curl -X POST -F name=john -F pass=doe http://localhost:3000 +# Form URL-Encoded +curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 -// curl -X POST "http://localhost:3000/?name=john&pass=doe" +# Multipart Form +curl -X POST -F name=john -F pass=doe http://localhost:3000 ``` -**The methods for the various bodies can also be used directly:** - -#### Form +### Form Binds the request form body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a Form body with a field called Pass, you would use a struct field of `form:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a form body with a field called `Pass`, you would use a struct field with `form:"pass"`. ```go title="Signature" func (b *Bind) Form(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { Name string `form:"name"` Pass string `form:"pass"` @@ -110,24 +108,25 @@ app.Post("/", func(c fiber.Ctx) error { // ... }) +``` -// Run tests with the following curl commands +Run tests with the following `curl` command: -// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 +```bash +curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 ``` -#### JSON +### JSON -Binds the request json body to a struct. +Binds the request JSON body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of `json:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called `Pass`, you would use a struct field with `json:"pass"`. ```go title="Signature" func (b *Bind) JSON(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { Name string `json:"name"` Pass string `json:"pass"` @@ -145,18 +144,19 @@ app.Post("/", func(c fiber.Ctx) error { // ... }) +``` -// Run tests with the following curl commands - -// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 +Run tests with the following `curl` command: +```bash +curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 ``` -#### MultipartForm +### MultipartForm Binds the request multipart form body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a MultipartForm body with a field called Pass, you would use a struct field of `form:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a multipart form body with a field called `Pass`, you would use a struct field with `form:"pass"`. ```go title="Signature" func (b *Bind) MultipartForm(out any) error @@ -181,18 +181,19 @@ app.Post("/", func(c fiber.Ctx) error { // ... }) +``` -// Run tests with the following curl commands - -// curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" localhost:3000 +Run tests with the following `curl` command: +```bash +curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" localhost:3000 ``` -#### XML +### XML -Binds the request xml form body to a struct. +Binds the request XML body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an XML body with a field called Pass, you would use a struct field of `xml:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an XML body with a field called `Pass`, you would use a struct field with `xml:"pass"`. ```go title="Signature" func (b *Bind) XML(out any) error @@ -217,27 +218,28 @@ app.Post("/", func(c fiber.Ctx) error { // ... }) +``` -// Run tests with the following curl commands +Run tests with the following `curl` command: -// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 +```bash +curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 ``` ### Cookie -This method is similar to [Body-Binding](#body), but for cookie parameters. -It is important to use the struct tag "cookie". For example, if you want to parse a cookie with a field called Age, you would use a struct field of `cookie:"age"`. +This method is similar to [Body Binding](#body), but for cookie parameters. +It is important to use the struct tag `cookie`. For example, if you want to parse a cookie with a field called `Age`, you would use a struct field with `cookie:"age"`. ```go title="Signature" func (b *Bind) Cookie(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { - Name string `cookie:"name"` - Age int `cookie:"age"` - Job bool `cookie:"job"` + Name string `cookie:"name"` + Age int `cookie:"age"` + Job bool `cookie:"job"` } app.Get("/", func(c fiber.Ctx) error { @@ -247,29 +249,32 @@ app.Get("/", func(c fiber.Ctx) error { return err } - log.Println(p.Name) // Joseph - log.Println(p.Age) // 23 - log.Println(p.Job) // true + log.Println(p.Name) // Joseph + log.Println(p.Age) // 23 + log.Println(p.Job) // true }) -// Run tests with the following curl command -// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/ +``` + +Run tests with the following `curl` command: + +```bash +curl --cookie "name=Joseph; age=23; job=true" http://localhost:8000/ ``` ### Header -This method is similar to [Body-Binding](#body), but for request headers. -It is important to use the struct tag "header". For example, if you want to parse a request header with a field called Pass, you would use a struct field of `header:"pass"`. +This method is similar to [Body Binding](#body), but for request headers. +It is important to use the struct tag `header`. For example, if you want to parse a request header with a field called `Pass`, you would use a struct field with `header:"pass"`. ```go title="Signature" func (b *Bind) Header(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { - Name string `header:"name"` - Pass string `header:"pass"` - Products []string `header:"products"` + Name string `header:"name"` + Pass string `header:"pass"` + Products []string `header:"products"` } app.Get("/", func(c fiber.Ctx) error { @@ -281,30 +286,32 @@ app.Get("/", func(c fiber.Ctx) error { log.Println(p.Name) // john log.Println(p.Pass) // doe - log.Println(p.Products) // [shoe, hat] + log.Println(p.Products) // [shoe hat] // ... }) -// Run tests with the following curl command +``` + +Run tests with the following `curl` command: -// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" +```bash +curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" ``` ### Query -This method is similar to [Body-Binding](#body), but for query parameters. -It is important to use the struct tag "query". For example, if you want to parse a query parameter with a field called Pass, you would use a struct field of `query:"pass"`. +This method is similar to [Body Binding](#body), but for query parameters. +It is important to use the struct tag `query`. For example, if you want to parse a query parameter with a field called `Pass`, you would use a struct field with `query:"pass"`. ```go title="Signature" func (b *Bind) Query(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { - Name string `query:"name"` - Pass string `query:"pass"` - Products []string `query:"products"` + Name string `query:"name"` + Pass string `query:"pass"` + Products []string `query:"products"` } app.Get("/", func(c fiber.Ctx) error { @@ -314,40 +321,41 @@ app.Get("/", func(c fiber.Ctx) error { return err } - log.Println(p.Name) // john - log.Println(p.Pass) // doe - // fiber.Config{EnableSplittingOnParsers: false} - default - log.Println(p.Products) // ["shoe,hat"] - // fiber.Config{EnableSplittingOnParsers: true} + log.Println(p.Name) // john + log.Println(p.Pass) // doe + // Depending on fiber.Config{EnableSplittingOnParsers: false} - default + log.Println(p.Products) // ["shoe,hat"] + // With fiber.Config{EnableSplittingOnParsers: true} // log.Println(p.Products) // ["shoe", "hat"] - // ... }) -// Run tests with the following curl command +``` -// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" +Run tests with the following `curl` command: + +```bash +curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" ``` :::info -For more parser settings please look here [Config](fiber.md#enablesplittingonparsers) +For more parser settings, please refer to [Config](fiber.md#enablesplittingonparsers) ::: ### RespHeader -This method is similar to [Body-Binding](#body), but for response headers. -It is important to use the struct tag "respHeader". For example, if you want to parse a request header with a field called Pass, you would use a struct field of `respHeader:"pass"`. +This method is similar to [Body Binding](#body), but for response headers. +It is important to use the struct tag `respHeader`. For example, if you want to parse a response header with a field called `Pass`, you would use a struct field with `respHeader:"pass"`. ```go title="Signature" -func (b *Bind) Header(out any) error +func (b *Bind) RespHeader(out any) error ``` ```go title="Example" -// Field names should start with an uppercase letter type Person struct { - Name string `respHeader:"name"` - Pass string `respHeader:"pass"` - Products []string `respHeader:"products"` + Name string `respHeader:"name"` + Pass string `respHeader:"pass"` + Products []string `respHeader:"products"` } app.Get("/", func(c fiber.Ctx) error { @@ -359,18 +367,22 @@ app.Get("/", func(c fiber.Ctx) error { log.Println(p.Name) // john log.Println(p.Pass) // doe - log.Println(p.Products) // [shoe, hat] + log.Println(p.Products) // [shoe hat] // ... }) -// Run tests with the following curl command +``` -// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" +Run tests with the following `curl` command: + +```bash +curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" ``` ### URI -This method is similar to [Body-Binding](#body), but for path parameters. It is important to use the struct tag "uri". For example, if you want to parse a path parameter with a field called Pass, you would use a struct field of uri:"pass" +This method is similar to [Body Binding](#body), but for path parameters. +It is important to use the struct tag `uri`. For example, if you want to parse a path parameter with a field called `Pass`, you would use a struct field with `uri:"pass"`. ```go title="Signature" func (b *Bind) URI(out any) error @@ -379,20 +391,24 @@ func (b *Bind) URI(out any) error ```go title="Example" // GET http://example.com/user/111 app.Get("/user/:id", func(c fiber.Ctx) error { - param := struct {ID uint `uri:"id"`}{} + param := struct { + ID uint `uri:"id"` + }{} - c.Bind().URI(¶m) // "{"id": 111}" + if err := c.Bind().URI(¶m); err != nil { + return err + } // ... + return c.SendString(fmt.Sprintf("User ID: %d", param.ID)) }) - ``` ## Custom To use custom binders, you have to use this method. -You can register them from [RegisterCustomBinder](./app.md#registercustombinder) method of Fiber instance. +You can register them using the [RegisterCustomBinder](./app.md#registercustombinder) method of the Fiber instance. ```go title="Signature" func (b *Bind) Custom(name string, dest any) error @@ -402,47 +418,50 @@ func (b *Bind) Custom(name string, dest any) error app := fiber.New() // My custom binder -customBinder := &customBinder{} -// Name of custom binder, which will be used as Bind().Custom("name") -func (*customBinder) Name() string { +type customBinder struct{} + +func (cb *customBinder) Name() string { return "custom" } -// Is used in the Body Bind method to check if the binder should be used for custom mime types -func (*customBinder) MIMETypes() []string { + +func (cb *customBinder) MIMETypes() []string { return []string{"application/yaml"} } -// Parse the body and bind it to the out interface -func (*customBinder) Parse(c Ctx, out any) error { - // parse yaml body + +func (cb *customBinder) Parse(c fiber.Ctx, out any) error { + // parse YAML body return yaml.Unmarshal(c.Body(), out) } + // Register custom binder -app.RegisterCustomBinder(customBinder) +app.RegisterCustomBinder(&customBinder{}) + +type User struct { + Name string `yaml:"name"` +} // curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John" -app.Post("/custom", func(c Ctx) error { +app.Post("/custom", func(c fiber.Ctx) error { var user User - // output: {Name:John} - // Custom binder is used by the name + // Use Custom binder by name if err := c.Bind().Custom("custom", &user); err != nil { return err } - // ... return c.JSON(user) }) ``` -Internally they are also used in the [Body](#body) method. -For this the MIMETypes method is used to check if the custom binder should be used for the given content type. +Internally, custom binders are also used in the [Body](#body) method. +The `MIMETypes` method is used to check if the custom binder should be used for the given content type. ## Options -For more control over the error handling, you can use the following methods. +For more control over error handling, you can use the following methods. ### Must -If you want to handle binder errors automatically, you can use Must. -If there's an error it'll return error and 400 as HTTP status. +If you want to handle binder errors automatically, you can use `Must`. +If there's an error, it will return the error and set HTTP status to `400 Bad Request`. ```go title="Signature" func (b *Bind) Must() *Bind @@ -450,8 +469,8 @@ func (b *Bind) Must() *Bind ### Should -To handle binder errors manually, you can prefer Should method. -It's default behavior of binder. +To handle binder errors manually, you can use the `Should` method. +It's the default behavior of the binder. ```go title="Signature" func (b *Bind) Should() *Bind @@ -459,7 +478,7 @@ func (b *Bind) Should() *Bind ## SetParserDecoder -Allow you to config BodyParser/QueryParser decoder, base on schema's options, providing possibility to add custom type for parsing. +Allows you to configure the BodyParser/QueryParser decoder based on schema options, providing the possibility to add custom types for parsing. ```go title="Signature" func SetParserDecoder(parserConfig fiber.ParserConfig{ @@ -477,34 +496,34 @@ func SetParserDecoder(parserConfig fiber.ParserConfig{ type CustomTime time.Time -// String() returns the time in string +// String returns the time in string format func (ct *CustomTime) String() string { t := time.Time(*ct).String() - return t - } - - // Register the converter for CustomTime type format as 2006-01-02 - var timeConverter = func(value string) reflect.Value { - fmt.Println("timeConverter", value) + return t +} + +// Converter for CustomTime type with format "2006-01-02" +var timeConverter = func(value string) reflect.Value { + fmt.Println("timeConverter:", value) if v, err := time.Parse("2006-01-02", value); err == nil { - return reflect.ValueOf(v) + return reflect.ValueOf(CustomTime(v)) } return reflect.Value{} } customTime := fiber.ParserType{ - Customtype: CustomTime{}, + CustomType: CustomTime{}, Converter: timeConverter, } -// Add setting to the Decoder +// Add custom type to the Decoder settings fiber.SetParserDecoder(fiber.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []fiber.ParserType{customTime}, ZeroEmpty: true, }) -// Example to use CustomType, you pause custom time format not in RFC3339 +// Example using CustomTime with non-RFC3339 format type Demo struct { Date CustomTime `form:"date" query:"date"` Title string `form:"title" query:"title"` @@ -513,31 +532,38 @@ type Demo struct { app.Post("/body", func(c fiber.Ctx) error { var d Demo - c.BodyParser(&d) - fmt.Println("d.Date", d.Date.String()) + if err := c.Bind().Body(&d); err != nil { + return err + } + fmt.Println("d.Date:", d.Date.String()) return c.JSON(d) }) app.Get("/query", func(c fiber.Ctx) error { var d Demo - c.QueryParser(&d) - fmt.Println("d.Date", d.Date.String()) + if err := c.Bind().Query(&d); err != nil { + return err + } + fmt.Println("d.Date:", d.Date.String()) return c.JSON(d) }) -// curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body +// Run tests with the following curl commands: -// curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20" +# Body Binding +curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body +# Query Binding +curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20" ``` ## Validation Validation is also possible with the binding methods. You can specify your validation rules using the `validate` struct tag. -Specify your struct validator in the [config](./fiber.md#structvalidator) +Specify your struct validator in the [config](./fiber.md#structvalidator). -Setup your validator in the config: +### Setup Your Validator in the Config ```go title="Example" import "github.com/go-playground/validator/v10" @@ -546,18 +572,18 @@ type structValidator struct { validate *validator.Validate } -// Validator needs to implement the Validate method +// Validate method implementation func (v *structValidator) Validate(out any) error { return v.validate.Struct(out) } -// Setup your validator in the config +// Setup your validator in the Fiber config app := fiber.New(fiber.Config{ StructValidator: &structValidator{validate: validator.New()}, }) ``` -Usage of the validation in the binding methods: +### Usage of Validation in Binding Methods ```go title="Example" type Person struct { @@ -568,7 +594,7 @@ type Person struct { app.Post("/", func(c fiber.Ctx) error { p := new(Person) - if err := c.Bind().JSON(p); err != nil {// <- here you receive the validation errors + if err := c.Bind().JSON(p); err != nil { // Receives validation errors return err } }) @@ -578,13 +604,13 @@ app.Post("/", func(c fiber.Ctx) error { You can set default values for fields in the struct by using the `default` struct tag. Supported types: -- bool -- float variants (float32, float64) -- int variants (int, int8, int16, int32, int64) -- uint variants (uint, uint8, uint16, uint32, uint64) -- string -- a slice of the above types. As shown in the example above, **| should be used to separate between slice items**. -- a pointer to one of the above types **(pointer to slice and slice of pointers are not supported)**. +- `bool` +- Float variants (`float32`, `float64`) +- Int variants (`int`, `int8`, `int16`, `int32`, `int64`) +- Uint variants (`uint`, `uint8`, `uint16`, `uint32`, `uint64`) +- `string` +- A slice of the above types. Use `|` to separate slice items. +- A pointer to one of the above types (**pointers to slices and slices of pointers are not supported**). ```go title="Example" type Person struct { @@ -600,13 +626,16 @@ app.Get("/", func(c fiber.Ctx) error { return err } - log.Println(p.Name) // john - log.Println(p.Pass) // doe - log.Println(p.Products) // ["shoe,hat"] + log.Println(p.Name) // john + log.Println(p.Pass) // doe + log.Println(p.Products) // ["shoe", "hat"] // ... }) -// Run tests with the following curl command +``` + +Run tests with the following `curl` command: -// curl "http://localhost:3000/?pass=doe" +```bash +curl "http://localhost:3000/?pass=doe" ``` diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 52d8144187..56a2ea61b0 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -354,15 +354,20 @@ app.Get("/hello", func(c fiber.Ctx) error { ## Context -Returns [\*fasthttp.RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) that is compatible with the context.Context interface that requires a deadline, a cancellation signal, and other values across API boundaries. +Context returns a context implementation that was set by user earlier or returns a non-nil, empty context, if it was not set earlier. ```go title="Signature" -func (c Ctx) Context() *fasthttp.RequestCtx +func (c Ctx) Context() context.Context ``` -:::info -Please read the [Fasthttp Documentation](https://pkg.go.dev/github.com/valyala/fasthttp?tab=doc) for more information. -::: +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + ctx := c.Context() + // ctx is context implementation set by user + + // ... +}) +``` ## Cookie @@ -1489,6 +1494,18 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` +## RequestCtx + +Returns [\*fasthttp.RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) that is compatible with the context.Context interface that requires a deadline, a cancellation signal, and other values across API boundaries. + +```go title="Signature" +func (c Ctx) RequestCtx() *fasthttp.RequestCtx +``` + +:::info +Please read the [Fasthttp Documentation](https://pkg.go.dev/github.com/valyala/fasthttp?tab=doc) for more information. +::: + ## Response Response return the [\*fasthttp.Response](https://godoc.org/github.com/valyala/fasthttp#Response) pointer @@ -1891,18 +1908,18 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## SetUserContext +## SetContext -Sets the user specified implementation for context interface. +Sets the user specified implementation for context.Context interface. ```go title="Signature" -func (c Ctx) SetUserContext(ctx context.Context) +func (c Ctx) SetContext(ctx context.Context) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { ctx := context.Background() - c.SetUserContext(ctx) + c.SetContext(ctx) // Here ctx could be any context implementation // ... @@ -2005,24 +2022,6 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## UserContext - -UserContext returns a context implementation that was set by user earlier -or returns a non-nil, empty context, if it was not set earlier. - -```go title="Signature" -func (c Ctx) UserContext() context.Context -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - ctx := c.UserContext() - // ctx is context implementation set by user - - // ... -}) -``` - ## Vary Adds the given header field to the [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) response header. This will append the header, if not already listed, otherwise leaves it listed in the current location. diff --git a/docs/api/hooks.md b/docs/api/hooks.md index 828d68a359..4852866602 100644 --- a/docs/api/hooks.md +++ b/docs/api/hooks.md @@ -7,7 +7,7 @@ sidebar_position: 7 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -With Fiber v2.30.0, you can execute custom user functions when to run some methods. Here is a list of these hooks: +With Fiber you can execute custom user functions at specific method execution points. Here is a list of these hooks: - [OnRoute](#onroute) - [OnName](#onname) @@ -21,7 +21,7 @@ With Fiber v2.30.0, you can execute custom user functions when to run some metho ## Constants ```go -// Handlers define a function to create hooks for Fiber. +// Handlers define functions to create hooks for Fiber. type OnRouteHandler = func(Route) error type OnNameHandler = OnRouteHandler type OnGroupHandler = func(Group) error @@ -34,7 +34,7 @@ type OnMountHandler = func(*App) error ## OnRoute -OnRoute is a hook to execute user functions on each route registration. Also you can get route properties by **route** parameter. +`OnRoute` is a hook to execute user functions on each route registration. You can access route properties via the **route** parameter. ```go title="Signature" func (h *Hooks) OnRoute(handler ...OnRouteHandler) @@ -42,10 +42,10 @@ func (h *Hooks) OnRoute(handler ...OnRouteHandler) ## OnName -OnName is a hook to execute user functions on each route naming. Also you can get route properties by **route** parameter. +`OnName` is a hook to execute user functions on each route naming. You can access route properties via the **route** parameter. :::caution -OnName only works with naming routes, not groups. +`OnName` only works with named routes, not groups. ::: ```go title="Signature" @@ -73,13 +73,11 @@ func main() { app.Hooks().OnName(func(r fiber.Route) error { fmt.Print("Name: " + r.Name + ", ") - return nil }) app.Hooks().OnName(func(r fiber.Route) error { fmt.Print("Method: " + r.Method + "\n") - return nil }) @@ -104,7 +102,7 @@ func main() { ## OnGroup -OnGroup is a hook to execute user functions on each group registration. Also you can get group properties by **group** parameter. +`OnGroup` is a hook to execute user functions on each group registration. You can access group properties via the **group** parameter. ```go title="Signature" func (h *Hooks) OnGroup(handler ...OnGroupHandler) @@ -112,10 +110,10 @@ func (h *Hooks) OnGroup(handler ...OnGroupHandler) ## OnGroupName -OnGroupName is a hook to execute user functions on each group naming. Also you can get group properties by **group** parameter. +`OnGroupName` is a hook to execute user functions on each group naming. You can access group properties via the **group** parameter. :::caution -OnGroupName only works with naming groups, not routes. +`OnGroupName` only works with named groups, not routes. ::: ```go title="Signature" @@ -124,7 +122,7 @@ func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) ## OnListen -OnListen is a hook to execute user functions on Listen, ListenTLS, Listener. +`OnListen` is a hook to execute user functions on `Listen`, `ListenTLS`, and `Listener`. ```go title="Signature" func (h *Hooks) OnListen(handler ...OnListenHandler) @@ -134,23 +132,35 @@ func (h *Hooks) OnListen(handler ...OnListenHandler) ```go -app := fiber.New(fiber.Config{ - DisableStartupMessage: true, -}) - -app.Hooks().OnListen(func(listenData fiber.ListenData) error { - if fiber.IsChild() { - return nil - } - scheme := "http" - if data.TLS { - scheme = "https" - } - log.Println(scheme + "://" + listenData.Host + ":" + listenData.Port) - return nil -}) - -app.Listen(":5000") +package main + +import ( + "log" + "os" + + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" +) + +func main() { + app := fiber.New(fiber.Config{ + DisableStartupMessage: true, + }) + + app.Hooks().OnListen(func(listenData fiber.ListenData) error { + if fiber.IsChild() { + return nil + } + scheme := "http" + if listenData.TLS { + scheme = "https" + } + log.Println(scheme + "://" + listenData.Host + ":" + listenData.Port) + return nil + }) + + app.Listen(":5000") +} ``` @@ -158,7 +168,7 @@ app.Listen(":5000") ## OnFork -OnFork is a hook to execute user functions on Fork. +`OnFork` is a hook to execute user functions on fork. ```go title="Signature" func (h *Hooks) OnFork(handler ...OnForkHandler) @@ -166,7 +176,7 @@ func (h *Hooks) OnFork(handler ...OnForkHandler) ## OnShutdown -OnShutdown is a hook to execute user functions after Shutdown. +`OnShutdown` is a hook to execute user functions after shutdown. ```go title="Signature" func (h *Hooks) OnShutdown(handler ...OnShutdownHandler) @@ -174,10 +184,10 @@ func (h *Hooks) OnShutdown(handler ...OnShutdownHandler) ## OnMount -OnMount is a hook to execute user function after mounting process. The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter. It works for app and group mounting. +`OnMount` is a hook to execute user functions after the mounting process. The mount event is fired when a sub-app is mounted on a parent app. The parent app is passed as a parameter. It works for both app and group mounting. ```go title="Signature" -func (h *Hooks) OnMount(handler ...OnMountHandler) +func (h *Hooks) OnMount(handler ...OnMountHandler) ``` @@ -193,24 +203,27 @@ import ( ) func main() { - app := New() + app := fiber.New() app.Get("/", testSimpleHandler).Name("x") - subApp := New() + subApp := fiber.New() subApp.Get("/test", testSimpleHandler) subApp.Hooks().OnMount(func(parent *fiber.App) error { - fmt.Print("Mount path of parent app: "+parent.MountPath()) - // ... - + fmt.Print("Mount path of parent app: " + parent.MountPath()) + // Additional custom logic... return nil }) app.Mount("/sub", subApp) } +func testSimpleHandler(c fiber.Ctx) error { + return c.SendString("Hello, Fiber!") +} + // Result: -// Mount path of parent app: +// Mount path of parent app: /sub ``` diff --git a/docs/api/log.md b/docs/api/log.md index 508be9a640..53f964a7dc 100644 --- a/docs/api/log.md +++ b/docs/api/log.md @@ -9,7 +9,7 @@ Logs serve as an essential tool for observing program behavior, diagnosing issue Fiber offers a default mechanism for logging to standard output. Additionally, it provides several global functions, including `log.Info`, `log.Errorf`, `log.Warnw`, among others, to facilitate comprehensive logging capabilities. -## Log levels +## Log Levels ```go const ( @@ -23,9 +23,9 @@ const ( ) ``` -## Custom log +## Custom Log -Fiber provides the `AllLogger` interface for adapting the various log libraries. +Fiber provides the `AllLogger` interface for adapting various log libraries. ```go type CommonLogger interface { @@ -41,13 +41,13 @@ type AllLogger interface { } ``` -## Print log +## Print Log -Note: The Fatal level method will terminate the program after printing the log message. Please use it with caution. +**Note:** The Fatal level method will terminate the program after printing the log message. Please use it with caution. ### Basic Logging -Logs of different levels can be directly printed. These will be entered into `messageKey`, with the default key being `msg`. +Logs of different levels can be directly printed. These logs will be entered into `messageKey`, with the default key being `msg`. ```go log.Info("Hello, World!") @@ -55,7 +55,7 @@ log.Debug("Are you OK?") log.Info("42 is the answer to life, the universe, and everything") log.Warn("We are under attack!") log.Error("Houston, we have a problem.") -log.Fatal("So Long, and Thanks for All the Fislog.") +log.Fatal("So Long, and Thanks for All the Fish.") log.Panic("The system is down.") ``` @@ -65,10 +65,10 @@ Logs of different levels can be formatted before printing. All such methods end ```go log.Debugf("Hello %s", "boy") -log.Infof("%d is the answer to life, the universe, and everything", 233) -log.Warnf("We are under attack %s!", "boss") +log.Infof("%d is the answer to life, the universe, and everything", 42) +log.Warnf("We are under attack, %s!", "boss") log.Errorf("%s, we have a problem.", "Master Shifu") -log.Fatalf("So Long, and Thanks for All the %s.", "banana") +log.Fatalf("So Long, and Thanks for All the %s.", "fish") ``` ### Key-Value Logging @@ -76,14 +76,14 @@ log.Fatalf("So Long, and Thanks for All the %s.", "banana") Print a message with key-value pairs. If the key and value are not paired correctly, the log will output `KEYVALS UNPAIRED`. ```go -log.Debugw("", "Hello", "boy") -log.Infow("", "number", 233) +log.Debugw("", "greeting", "Hello", "target", "boy") +log.Infow("", "number", 42) log.Warnw("", "job", "boss") log.Errorw("", "name", "Master Shifu") -log.Fatalw("", "fruit", "banana") +log.Fatalw("", "fruit", "fish") ``` -## Global log +## Global Log For projects that require a simple, global logging function to print messages at any time, Fiber provides a global log. @@ -96,7 +96,7 @@ log.Warn("warn") These global log functions allow you to log messages conveniently throughout your project. -The above example uses the default `log.DefaultLogger` for standard output. You can also find various pre-implemented adapters under the [contrib](https://github.com/gofiber/contrib) package such as `fiberzap` and `fiberzerolog`, or you can implement your own logger and set it as the global logger using `log.SetLogger`.This flexibility allows you to tailor the logging behavior to suit your project's needs. +The above example uses the default `log.DefaultLogger` for standard output. You can also find various pre-implemented adapters under the [contrib](https://github.com/gofiber/contrib) package such as `fiberzap` and `fiberzerolog`, or you can implement your own logger and set it as the global logger using `log.SetLogger`. This flexibility allows you to tailor the logging behavior to suit your project's needs. Here's an example using a custom logger: @@ -106,22 +106,25 @@ import ( fiberlog "github.com/gofiber/fiber/v3/log" ) -var _ log.AllLogger = (*customLogger)(nil) +var _ fiberlog.AllLogger = (*customLogger)(nil) type customLogger struct { stdlog *log.Logger } -// ... -// inject your custom logger -fiberlog.SetLogger(customLogger) +// Implement required methods for the AllLogger interface... + +// Inject your custom logger +fiberlog.SetLogger(&customLogger{ + stdlog: log.New(os.Stdout, "CUSTOM ", log.LstdFlags), +}) ``` ## Set Level `log.SetLevel` sets the minimum level of logs that will be output. The default log level is `LevelTrace`. -Note that this method is not **concurrent-safe**. +**Note:** This method is not **concurrent-safe**. ```go import "github.com/gofiber/fiber/v3/log" @@ -131,14 +134,14 @@ log.SetLevel(log.LevelInfo) Setting the log level allows you to control the verbosity of the logs, filtering out messages below the specified level. -## Set output +## Set Output `log.SetOutput` sets the output destination of the logger. By default, the logger outputs logs to the console. -### Writing logs to stderr +### Writing Logs to Stderr ```go -var logger AllLogger = &defaultLogger{ +var logger fiberlog.AllLogger = &defaultLogger{ stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), depth: 4, } @@ -146,31 +149,34 @@ var logger AllLogger = &defaultLogger{ This allows you to customize where the logs are written, such as to a file, an external logging service, or any other desired destination. -### Writing logs to a file +### Writing Logs to a File -Set the output destination to the file, in this case `test.log`: +Set the output destination to a file, in this case `test.log`: ```go // Output to ./test.log file f, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - return + log.Fatal("Failed to open log file:", err) } log.SetOutput(f) ``` -### Writing logs to both console and file +### Writing Logs to Both Console and File The following example will write the logs to both `test.log` and `stdout`: ```go // Output to ./test.log file -file, _ := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +file, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +if err != nil { + log.Fatal("Failed to open log file:", err) +} iw := io.MultiWriter(os.Stdout, file) log.SetOutput(iw) ``` -## Bind context +## Bind Context To bind a logger to a specific context, use the following method. This will return a `CommonLogger` instance that is bound to the specified context. diff --git a/docs/api/redirect.md b/docs/api/redirect.md index 79faa36d45..fa8bd1b3cc 100644 --- a/docs/api/redirect.md +++ b/docs/api/redirect.md @@ -6,14 +6,13 @@ sidebar_position: 5 toc_max_heading_level: 5 --- -Is used to redirect the ctx(request) to a different URL/Route. +The redirect methods are used to redirect the context (request) to a different URL or route. ## Redirect Methods ### To -Redirects to the URL derived from the specified path, with specified [status](#status), a positive integer that -corresponds to an HTTP status code. +Redirects to the URL derived from the specified path, with a specified [status](#status), a positive integer that corresponds to an HTTP status code. :::info If **not** specified, status defaults to **302 Found**. @@ -49,10 +48,10 @@ app.Get("/", func(c fiber.Ctx) error { ### Route -Redirects to the specific route along with the parameters and queries. +Redirects to a specific route along with the parameters and queries. :::info -If you want to send queries and params to route, you must use the [**RedirectConfig**](#redirectconfig) struct. +If you want to send queries and params to a route, you must use the [**RedirectConfig**](#redirectconfig) struct. ::: ```go title="Signature" @@ -71,7 +70,7 @@ app.Get("/", func(c fiber.Ctx) error { app.Get("/with-queries", func(c fiber.Ctx) error { // /user/fiber?data[0][name]=john&data[0][age]=10&test=doe - return c.Route("user", RedirectConfig{ + return c.Redirect().Route("user", fiber.RedirectConfig{ Params: fiber.Map{ "name": "fiber", }, @@ -90,8 +89,7 @@ app.Get("/user/:name", func(c fiber.Ctx) error { ### Back -Redirects back to refer URL. It redirects to fallback URL if refer header doesn't exists, with specified status, a -positive integer that corresponds to an HTTP status code. +Redirects back to the referer URL. It redirects to a fallback URL if the referer header doesn't exist, with a specified status, a positive integer that corresponds to an HTTP status code. :::info If **not** specified, status defaults to **302 Found**. @@ -105,6 +103,7 @@ func (r *Redirect) Back(fallback string) error app.Get("/", func(c fiber.Ctx) error { return c.SendString("Home page") }) + app.Get("/test", func(c fiber.Ctx) error { c.Set("Content-Type", "text/html") return c.SendString(`Back`) @@ -118,7 +117,7 @@ app.Get("/back", func(c fiber.Ctx) error { ## Controls :::info -Method are **chainable**. +Methods are **chainable**. ::: ### Status @@ -126,7 +125,7 @@ Method are **chainable**. Sets the HTTP status code for the redirect. :::info -Is used in conjunction with [**To**](#to), [**Route**](#route) and [**Back**](#back) methods. +It is used in conjunction with [**To**](#to), [**Route**](#route), and [**Back**](#back) methods. ::: ```go title="Signature" @@ -145,11 +144,11 @@ app.Get("/coffee", func(c fiber.Ctx) error { Sets the configuration for the redirect. :::info -Is used in conjunction with the [**Route**](#route) method. +It is used in conjunction with the [**Route**](#route) method. ::: -```go -// RedirectConfig A config to use with Redirect().Route() +```go title="Definition" +// RedirectConfig is a config to use with Redirect().Route() type RedirectConfig struct { Params fiber.Map // Route parameters Queries map[string]string // Query map @@ -158,7 +157,7 @@ type RedirectConfig struct { ### Flash Message -Similar to [Laravel](https://laravel.com/docs/11.x/redirects#redirecting-with-flashed-session-data) we can flash a message and retrieve it in the next request. +Similar to [Laravel](https://laravel.com/docs/11.x/redirects#redirecting-with-flashed-session-data), we can flash a message and retrieve it in the next request. #### Messages @@ -177,7 +176,7 @@ app.Get("/", func(c fiber.Ctx) error { #### Message -Get flash message by key. Check [With](#with) for more information. +Get a flash message by key. Check [With](#with) for more information. ```go title="Signature" func (r *Redirect) Message(key string) *Redirect @@ -241,10 +240,9 @@ app.Get("/", func(c fiber.Ctx) error { #### WithInput -You can send input data by using `WithInput()`. -They will be sent as a cookie. +You can send input data by using `WithInput()`. They will be sent as a cookie. -This method can send form, multipart form, query data to redirected route depending on the request content type. +This method can send form, multipart form, or query data to the redirected route depending on the request content type. ```go title="Signature" func (r *Redirect) WithInput() *Redirect diff --git a/docs/intro.md b/docs/intro.md index 7e0e1798c9..4dc763df36 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -4,15 +4,15 @@ id: welcome title: πŸ‘‹ Welcome sidebar_position: 1 --- -An online API documentation with examples so you can start building web apps with Fiber right away! +Welcome to the online API documentation for Fiber, complete with examples to help you start building web applications with Fiber right away! -**Fiber** is an [Express](https://github.com/expressjs/express) inspired **web framework** built on top of [Fasthttp](https://github.com/valyala/fasthttp), the **fastest** HTTP engine for [Go](https://go.dev/doc/). Designed to **ease** things up for **fast** development with **zero memory allocation** and **performance** in mind. +**Fiber** is an [Express](https://github.com/expressjs/express)-inspired **web framework** built on top of [Fasthttp](https://github.com/valyala/fasthttp), the **fastest** HTTP engine for [Go](https://go.dev/doc/). It is designed to facilitate rapid development with **zero memory allocations** and a strong focus on **performance**. -These docs are for **Fiber v3**, which was released on **March XX, 2024**. +These docs are for **Fiber v3**, which was released on **Month xx, 202x**. ### Installation -First of all, [download](https://go.dev/dl/) and install Go. `1.22` or higher is required. +First, [download](https://go.dev/dl/) and install Go. Version `1.22` or higher is required. Installation is done using the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: @@ -22,7 +22,7 @@ go get github.com/gofiber/fiber/v3 ### Zero Allocation -Fiber is optimized for **high-performance**, meaning values returned from **fiber.Ctx** are **not** immutable by default and **will** be re-used across requests. As a rule of thumb, you **must** only use context values within the handler and **must not** keep any references. Once you return from the handler, any values obtained from the context will be re-used in future requests. Here is an example: +Fiber is optimized for **high performance**, meaning values returned from **fiber.Ctx** are **not** immutable by default and **will** be reused across requests. As a rule of thumb, you **must** only use context values within the handler and **must not** keep any references. Once you return from the handler, any values obtained from the context will be reused in future requests. Here is an example: ```go func handler(c fiber.Ctx) error { @@ -44,13 +44,13 @@ func handler(c fiber.Ctx) error { buffer := make([]byte, len(result)) copy(buffer, result) resultCopy := string(buffer) - // Variable is now valid forever + // Variable is now valid indefinitely // ... } ``` -We created a custom `CopyString` function that does the above and is available under [gofiber/utils](https://github.com/gofiber/utils). +We created a custom `CopyString` function that performs the above and is available under [gofiber/utils](https://github.com/gofiber/utils). ```go app.Get("/:foo", func(c fiber.Ctx) error { @@ -61,7 +61,7 @@ app.Get("/:foo", func(c fiber.Ctx) error { }) ``` -Alternatively, you can also use the `Immutable` setting. It will make all values returned from the context immutable, allowing you to persist them anywhere. Of course, this comes at the cost of performance. +Alternatively, you can enable the `Immutable` setting. This makes all values returned from the context immutable, allowing you to persist them anywhere. Note that this comes at the cost of performance. ```go app := fiber.New(fiber.Config{ @@ -69,11 +69,11 @@ app := fiber.New(fiber.Config{ }) ``` -For more information, please check [**\#426**](https://github.com/gofiber/fiber/issues/426), [**\#185**](https://github.com/gofiber/fiber/issues/185) and [**\#3012**](https://github.com/gofiber/fiber/issues/3012). +For more information, please refer to [#426](https://github.com/gofiber/fiber/issues/426), [#185](https://github.com/gofiber/fiber/issues/185), and [#3012](https://github.com/gofiber/fiber/issues/3012). ### Hello, World -Embedded below is essentially the most straightforward **Fiber** app you can create: +Below is the most straightforward **Fiber** application you can create: ```go package main @@ -95,15 +95,15 @@ func main() { go run server.go ``` -Browse to `http://localhost:3000` and you should see `Hello, World!` on the page. +Browse to `http://localhost:3000` and you should see `Hello, World!` displayed on the page. -### Basic routing +### Basic Routing -Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (`GET`, `PUT`, `POST`, etc.). +Routing determines how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (`GET`, `PUT`, `POST`, etc.). Each route can have **multiple handler functions** that are executed when the route is matched. -Route definition takes the following structures: +Route definitions follow the structure below: ```go // Function signature @@ -115,10 +115,10 @@ app.Method(path string, ...func(fiber.Ctx) error) - `path` is a virtual path on the server - `func(fiber.Ctx) error` is a callback function containing the [Context](https://docs.gofiber.io/api/ctx) executed when the route is matched -#### Simple route +#### Simple Route ```go -// Respond with "Hello, World!" on root path, "/" +// Respond with "Hello, World!" on root path "/" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) @@ -131,11 +131,11 @@ app.Get("/", func(c fiber.Ctx) error { app.Get("/:value", func(c fiber.Ctx) error { return c.SendString("value: " + c.Params("value")) - // => Get request with value: hello world + // => Response: "value: hello world" }) ``` -#### Optional parameter +#### Optional Parameter ```go // GET http://localhost:3000/john @@ -143,9 +143,10 @@ app.Get("/:value", func(c fiber.Ctx) error { app.Get("/:name?", func(c fiber.Ctx) error { if c.Params("name") != "" { return c.SendString("Hello " + c.Params("name")) - // => Hello john + // => Response: "Hello john" } return c.SendString("Where is john?") + // => Response: "Where is john?" }) ``` @@ -156,27 +157,33 @@ app.Get("/:name?", func(c fiber.Ctx) error { app.Get("/api/*", func(c fiber.Ctx) error { return c.SendString("API path: " + c.Params("*")) - // => API path: user/john + // => Response: "API path: user/john" }) ``` -### Static files +### Static Files -To serve static files such as **images**, **CSS**, and **JavaScript** files, replace your function handler with a file or directory string. -You can check out [static middleware](./middleware/static.md) for more information. -Function signature: +To serve static files such as **images**, **CSS**, and **JavaScript** files, use the `Static` method with a directory path. For more information, refer to the [static middleware](./middleware/static.md). Use the following code to serve files in a directory named `./public`: ```go -app := fiber.New() +package main + +import ( + "github.com/gofiber/fiber/v3" +) -app.Get("/*", static.New("./public")) +func main() { + app := fiber.New() -app.Listen(":3000") + app.Static("/", "./public") + + app.Listen(":3000") +} ``` -Now, you can load the files that are in the `./public` directory: +Now, you can access the files in the `./public` directory via your browser: ```bash http://localhost:3000/hello.html diff --git a/docs/middleware/requestid.md b/docs/middleware/requestid.md index 739a4a6190..01ec569e3c 100644 --- a/docs/middleware/requestid.md +++ b/docs/middleware/requestid.md @@ -49,6 +49,16 @@ func handler(c fiber.Ctx) error { } ``` +In version v3, Fiber will inject `requestID` into the built-in `Context` of Go. + +```go +func handler(c fiber.Ctx) error { + id := requestid.FromContext(c.Context()) + log.Printf("Request ID: %s", id) + return c.SendString("Hello, World!") +} +``` + ## Config | Property | Type | Description | Default | diff --git a/docs/middleware/timeout.md b/docs/middleware/timeout.md index 8f94f0567f..87421d20b9 100644 --- a/docs/middleware/timeout.md +++ b/docs/middleware/timeout.md @@ -8,7 +8,7 @@ There exist two distinct implementations of timeout middleware [Fiber](https://g ## New -As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` and pass it in `UserContext`. +As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` which is then used with `c.Context()`. If the context passed executions (eg. DB ops, Http calls) takes longer than the given duration to return, the timeout error is set and forwarded to the centralized `ErrorHandler`. @@ -38,7 +38,7 @@ func main() { app := fiber.New() h := func(c fiber.Ctx) error { sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") - if err := sleepWithContext(c.UserContext(), sleepTime); err != nil { + if err := sleepWithContext(c.Context(), sleepTime); err != nil { return fmt.Errorf("%w: execution error", err) } return nil @@ -84,7 +84,7 @@ func main() { app := fiber.New() h := func(c fiber.Ctx) error { sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") - if err := sleepWithContextWithCustomError(c.UserContext(), sleepTime); err != nil { + if err := sleepWithContextWithCustomError(c.Context(), sleepTime); err != nil { return fmt.Errorf("%w: execution error", err) } return nil @@ -116,7 +116,7 @@ func main() { db, _ := gorm.Open(postgres.Open("postgres://localhost/foodb"), &gorm.Config{}) handler := func(ctx fiber.Ctx) error { - tran := db.WithContext(ctx.UserContext()).Begin() + tran := db.WithContext(ctx.Context()).Begin() if tran = tran.Exec("SELECT pg_sleep(50)"); tran.Error != nil { return tran.Error diff --git a/docs/whats_new.md b/docs/whats_new.md index b46e8922db..cae1aa4766 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -264,6 +264,9 @@ DRAFT section - Format -> Param: body interface{} -> handlers ...ResFmt - Redirect -> c.Redirect().To() - SendFile now supports different configurations using the config parameter. +- Context has been renamed to RequestCtx which corresponds to the FastHTTP Request Context. +- UserContext has been renamed to Context which returns a context.Context object. +- SetUserContext has been renamed to SetContext. --- diff --git a/middleware/adaptor/adaptor.go b/middleware/adaptor/adaptor.go index 867f440adf..42b72101f9 100644 --- a/middleware/adaptor/adaptor.go +++ b/middleware/adaptor/adaptor.go @@ -34,7 +34,7 @@ func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler { func HTTPHandler(h http.Handler) fiber.Handler { return func(c fiber.Ctx) error { handler := fasthttpadaptor.NewFastHTTPHandler(h) - handler(c.Context()) + handler(c.RequestCtx()) return nil } } @@ -43,7 +43,7 @@ func HTTPHandler(h http.Handler) fiber.Handler { // forServer should be set to true when the http.Request is going to be passed to a http.Handler. func ConvertRequest(c fiber.Ctx, forServer bool) (*http.Request, error) { var req http.Request - if err := fasthttpadaptor.ConvertRequest(c.Context(), &req, forServer); err != nil { + if err := fasthttpadaptor.ConvertRequest(c.RequestCtx(), &req, forServer); err != nil { return nil, err //nolint:wrapcheck // This must not be wrapped } return &req, nil @@ -108,7 +108,7 @@ func HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler { c.Request().Header.Set(key, v) } } - CopyContextToFiberContext(r.Context(), c.Context()) + CopyContextToFiberContext(r.Context(), c.RequestCtx()) }) if err := HTTPHandler(mw(nextHandler))(c); err != nil { diff --git a/middleware/adaptor/adaptor_test.go b/middleware/adaptor/adaptor_test.go index 990d421dec..67c306febf 100644 --- a/middleware/adaptor/adaptor_test.go +++ b/middleware/adaptor/adaptor_test.go @@ -162,7 +162,7 @@ func Test_HTTPMiddleware(t *testing.T) { app := fiber.New() app.Use(HTTPMiddleware(nethttpMW)) app.Post("/", func(c fiber.Ctx) error { - value := c.Context().Value(TestContextKey) + value := c.RequestCtx().Value(TestContextKey) val, ok := value.(string) if !ok { t.Error("unexpected error on type-assertion") @@ -170,7 +170,7 @@ func Test_HTTPMiddleware(t *testing.T) { if value != nil { c.Set("context_okay", val) } - value = c.Context().Value(TestContextSecondKey) + value = c.RequestCtx().Value(TestContextSecondKey) if value != nil { val, ok := value.(string) if !ok { @@ -316,12 +316,12 @@ func testFiberToHandlerFunc(t *testing.T, checkDefaultPort bool, app ...*fiber.A fiberH := func(c fiber.Ctx) error { callsCount++ require.Equal(t, expectedMethod, c.Method(), "Method") - require.Equal(t, expectedRequestURI, string(c.Context().RequestURI()), "RequestURI") - require.Equal(t, expectedContentLength, c.Context().Request.Header.ContentLength(), "ContentLength") + require.Equal(t, expectedRequestURI, string(c.RequestCtx().RequestURI()), "RequestURI") + require.Equal(t, expectedContentLength, c.RequestCtx().Request.Header.ContentLength(), "ContentLength") require.Equal(t, expectedHost, c.Hostname(), "Host") require.Equal(t, expectedHost, string(c.Request().Header.Host()), "Host") require.Equal(t, "http://"+expectedHost, c.BaseURL(), "BaseURL") - require.Equal(t, expectedRemoteAddr, c.Context().RemoteAddr().String(), "RemoteAddr") + require.Equal(t, expectedRemoteAddr, c.RequestCtx().RemoteAddr().String(), "RemoteAddr") body := string(c.Body()) require.Equal(t, expectedBody, body, "Body") @@ -392,8 +392,8 @@ func Test_FiberHandler_RequestNilBody(t *testing.T) { fiberH := func(c fiber.Ctx) error { callsCount++ require.Equal(t, expectedMethod, c.Method(), "Method") - require.Equal(t, expectedRequestURI, string(c.Context().RequestURI()), "RequestURI") - require.Equal(t, expectedContentLength, c.Context().Request.Header.ContentLength(), "ContentLength") + require.Equal(t, expectedRequestURI, string(c.RequestCtx().RequestURI()), "RequestURI") + require.Equal(t, expectedContentLength, c.RequestCtx().Request.Header.ContentLength(), "ContentLength") _, err := c.Write([]byte("request body is nil")) return err diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 8f00f1f156..22ab0e2895 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -894,7 +894,7 @@ func Test_Cache_MaxBytesSizes(t *testing.T) { })) app.Get("/*", func(c fiber.Ctx) error { - path := c.Context().URI().LastPathSegment() + path := c.RequestCtx().URI().LastPathSegment() size, err := strconv.Atoi(string(path)) require.NoError(t, err) return c.Send(make([]byte, size)) diff --git a/middleware/compress/compress.go b/middleware/compress/compress.go index 00f109148e..6bd1ae09c4 100644 --- a/middleware/compress/compress.go +++ b/middleware/compress/compress.go @@ -56,7 +56,7 @@ func New(config ...Config) fiber.Handler { } // Compress response - compressor(c.Context()) + compressor(c.RequestCtx()) // Return from handler return nil diff --git a/middleware/etag/etag.go b/middleware/etag/etag.go index f28a33cec5..a00dd62bf4 100644 --- a/middleware/etag/etag.go +++ b/middleware/etag/etag.go @@ -80,7 +80,7 @@ func New(config ...Config) fiber.Handler { // Check if server's ETag is weak if bytes.Equal(clientEtag[2:], etag) || bytes.Equal(clientEtag[2:], etag[2:]) { // W/1 == 1 || W/1 == W/1 - c.Context().ResetBody() + c.RequestCtx().ResetBody() return c.SendStatus(fiber.StatusNotModified) } @@ -92,7 +92,7 @@ func New(config ...Config) fiber.Handler { if bytes.Contains(clientEtag, etag) { // 1 == 1 - c.Context().ResetBody() + c.RequestCtx().ResetBody() return c.SendStatus(fiber.StatusNotModified) } diff --git a/middleware/expvar/expvar.go b/middleware/expvar/expvar.go index 8ea09e1a77..bcc92c58cd 100644 --- a/middleware/expvar/expvar.go +++ b/middleware/expvar/expvar.go @@ -25,7 +25,7 @@ func New(config ...Config) fiber.Handler { return c.Next() } if path == "/debug/vars" { - expvarhandler.ExpvarHandler(c.Context()) + expvarhandler.ExpvarHandler(c.RequestCtx()) return nil } diff --git a/middleware/idempotency/idempotency.go b/middleware/idempotency/idempotency.go index 923ce5ce9a..096384638a 100644 --- a/middleware/idempotency/idempotency.go +++ b/middleware/idempotency/idempotency.go @@ -51,7 +51,7 @@ func New(config ...Config) fiber.Handler { for header, vals := range res.Headers { for _, val := range vals { - c.Context().Response.Header.Add(header, val) + c.RequestCtx().Response.Header.Add(header, val) } } diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 745c35bd42..946db05d80 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -638,7 +638,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) { app.Get("/", func(c fiber.Ctx) error { c.Set("Connection", "keep-alive") c.Set("Transfer-Encoding", "chunked") - c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { + c.RequestCtx().SetBodyStreamWriter(func(w *bufio.Writer) { var i int for { i++ @@ -809,7 +809,7 @@ func Benchmark_Logger(b *testing.B) { app.Get("/", func(c fiber.Ctx) error { c.Set("Connection", "keep-alive") c.Set("Transfer-Encoding", "chunked") - c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { + c.RequestCtx().SetBodyStreamWriter(func(w *bufio.Writer) { var i int for { i++ @@ -964,7 +964,7 @@ func Benchmark_Logger_Parallel(b *testing.B) { app.Get("/", func(c fiber.Ctx) error { c.Set("Connection", "keep-alive") c.Set("Transfer-Encoding", "chunked") - c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { + c.RequestCtx().SetBodyStreamWriter(func(w *bufio.Writer) { var i int for { i++ diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go index 13b01b533c..a8be0c059f 100644 --- a/middleware/pprof/pprof.go +++ b/middleware/pprof/pprof.go @@ -48,27 +48,27 @@ func New(config ...Config) fiber.Handler { // Switch on trimmed path against constant strings switch path { case "/": - pprofIndex(c.Context()) + pprofIndex(c.RequestCtx()) case "/cmdline": - pprofCmdline(c.Context()) + pprofCmdline(c.RequestCtx()) case "/profile": - pprofProfile(c.Context()) + pprofProfile(c.RequestCtx()) case "/symbol": - pprofSymbol(c.Context()) + pprofSymbol(c.RequestCtx()) case "/trace": - pprofTrace(c.Context()) + pprofTrace(c.RequestCtx()) case "/allocs": - pprofAllocs(c.Context()) + pprofAllocs(c.RequestCtx()) case "/block": - pprofBlock(c.Context()) + pprofBlock(c.RequestCtx()) case "/goroutine": - pprofGoroutine(c.Context()) + pprofGoroutine(c.RequestCtx()) case "/heap": - pprofHeap(c.Context()) + pprofHeap(c.RequestCtx()) case "/mutex": - pprofMutex(c.Context()) + pprofMutex(c.RequestCtx()) case "/threadcreate": - pprofThreadcreate(c.Context()) + pprofThreadcreate(c.RequestCtx()) default: // pprof index only works with trailing slash if strings.HasSuffix(path, "/") { diff --git a/middleware/redirect/redirect.go b/middleware/redirect/redirect.go index 4fb7bb8830..0e95095dd3 100644 --- a/middleware/redirect/redirect.go +++ b/middleware/redirect/redirect.go @@ -30,7 +30,7 @@ func New(config ...Config) fiber.Handler { for k, v := range cfg.rulesRegex { replacer := captureTokens(k, c.Path()) if replacer != nil { - queryString := string(c.Context().QueryArgs().QueryString()) + queryString := string(c.RequestCtx().QueryArgs().QueryString()) if queryString != "" { queryString = "?" + queryString } diff --git a/middleware/requestid/requestid.go b/middleware/requestid/requestid.go index 8e521dc650..ef67e6f21c 100644 --- a/middleware/requestid/requestid.go +++ b/middleware/requestid/requestid.go @@ -1,7 +1,10 @@ package requestid import ( + "context" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" ) // The contextKey type is unexported to prevent collisions with context keys defined in @@ -36,6 +39,10 @@ func New(config ...Config) fiber.Handler { // Add the request ID to locals c.Locals(requestIDKey, rid) + // Add the request ID to UserContext + ctx := context.WithValue(c.Context(), requestIDKey, rid) + c.SetContext(ctx) + // Continue stack return c.Next() } @@ -43,9 +50,21 @@ func New(config ...Config) fiber.Handler { // FromContext returns the request ID from context. // If there is no request ID, an empty string is returned. -func FromContext(c fiber.Ctx) string { - if rid, ok := c.Locals(requestIDKey).(string); ok { - return rid +// Supported context types: +// - fiber.Ctx: Retrieves request ID from Locals +// - context.Context: Retrieves request ID from context values +func FromContext(c any) string { + switch ctx := c.(type) { + case fiber.Ctx: + if rid, ok := ctx.Locals(requestIDKey).(string); ok { + return rid + } + case context.Context: + if rid, ok := ctx.Value(requestIDKey).(string); ok { + return rid + } + default: + log.Errorf("Unsupported context type: %T. Expected fiber.Ctx or context.Context", c) } return "" } diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go index c739407be0..ad36884aca 100644 --- a/middleware/requestid/requestid_test.go +++ b/middleware/requestid/requestid_test.go @@ -51,26 +51,59 @@ func Test_RequestID_Next(t *testing.T) { require.Equal(t, fiber.StatusNotFound, resp.StatusCode) } -// go test -run Test_RequestID_Locals +// go test -run Test_RequestID_FromContext func Test_RequestID_FromContext(t *testing.T) { t.Parallel() + reqID := "ThisIsARequestId" - app := fiber.New() - app.Use(New(Config{ - Generator: func() string { - return reqID + type args struct { + inputFunc func(c fiber.Ctx) any + } + + tests := []struct { + args args + name string + }{ + { + name: "From fiber.Ctx", + args: args{ + inputFunc: func(c fiber.Ctx) any { + return c + }, + }, }, - })) - - var ctxVal string - - app.Use(func(c fiber.Ctx) error { - ctxVal = FromContext(c) - return c.Next() - }) - - _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) - require.NoError(t, err) - require.Equal(t, reqID, ctxVal) + { + name: "From context.Context", + args: args{ + inputFunc: func(c fiber.Ctx) any { + return c.Context() + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + app := fiber.New() + app.Use(New(Config{ + Generator: func() string { + return reqID + }, + })) + + var ctxVal string + + app.Use(func(c fiber.Ctx) error { + ctxVal = FromContext(tt.args.inputFunc(c)) + return c.Next() + }) + + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + require.NoError(t, err) + require.Equal(t, reqID, ctxVal) + }) + } } diff --git a/middleware/static/static.go b/middleware/static/static.go index 6cbdbd3d22..7afc77980f 100644 --- a/middleware/static/static.go +++ b/middleware/static/static.go @@ -114,7 +114,7 @@ func New(root string, cfg ...Config) fiber.Handler { }) // Serve file - fileHandler(c.Context()) + fileHandler(c.RequestCtx()) // Sets the response Content-Disposition header to attachment if the Download option is true if config.Download { @@ -122,11 +122,11 @@ func New(root string, cfg ...Config) fiber.Handler { } // Return request if found and not forbidden - status := c.Context().Response.StatusCode() + status := c.RequestCtx().Response.StatusCode() if status != fiber.StatusNotFound && status != fiber.StatusForbidden { if len(cacheControlValue) > 0 { - c.Context().Response.Header.Set(fiber.HeaderCacheControl, cacheControlValue) + c.RequestCtx().Response.Header.Set(fiber.HeaderCacheControl, cacheControlValue) } if config.ModifyResponse != nil { @@ -142,9 +142,9 @@ func New(root string, cfg ...Config) fiber.Handler { } // Reset response to default - c.Context().SetContentType("") // Issue #420 - c.Context().Response.SetStatusCode(fiber.StatusOK) - c.Context().Response.SetBodyString("") + c.RequestCtx().SetContentType("") // Issue #420 + c.RequestCtx().Response.SetStatusCode(fiber.StatusOK) + c.RequestCtx().Response.SetBodyString("") // Next middleware return c.Next() diff --git a/middleware/timeout/timeout.go b/middleware/timeout/timeout.go index 5a9711ce22..a88f2e90b1 100644 --- a/middleware/timeout/timeout.go +++ b/middleware/timeout/timeout.go @@ -11,9 +11,9 @@ import ( // New implementation of timeout middleware. Set custom errors(context.DeadlineExceeded vs) for get fiber.ErrRequestTimeout response. func New(h fiber.Handler, t time.Duration, tErrs ...error) fiber.Handler { return func(ctx fiber.Ctx) error { - timeoutContext, cancel := context.WithTimeout(ctx.UserContext(), t) + timeoutContext, cancel := context.WithTimeout(ctx.Context(), t) defer cancel() - ctx.SetUserContext(timeoutContext) + ctx.SetContext(timeoutContext) if err := h(ctx); err != nil { if errors.Is(err, context.DeadlineExceeded) { return fiber.ErrRequestTimeout diff --git a/middleware/timeout/timeout_test.go b/middleware/timeout/timeout_test.go index b08445eb2a..2e1756184c 100644 --- a/middleware/timeout/timeout_test.go +++ b/middleware/timeout/timeout_test.go @@ -20,7 +20,7 @@ func Test_WithContextTimeout(t *testing.T) { h := New(func(c fiber.Ctx) error { sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms") require.NoError(t, err) - if err := sleepWithContext(c.UserContext(), sleepTime, context.DeadlineExceeded); err != nil { + if err := sleepWithContext(c.Context(), sleepTime, context.DeadlineExceeded); err != nil { return fmt.Errorf("%w: l2 wrap", fmt.Errorf("%w: l1 wrap ", err)) } return nil @@ -52,7 +52,7 @@ func Test_WithContextTimeoutWithCustomError(t *testing.T) { h := New(func(c fiber.Ctx) error { sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms") require.NoError(t, err) - if err := sleepWithContext(c.UserContext(), sleepTime, ErrFooTimeOut); err != nil { + if err := sleepWithContext(c.Context(), sleepTime, ErrFooTimeOut); err != nil { return fmt.Errorf("%w: execution error", err) } return nil diff --git a/redirect.go b/redirect.go index ebbcb499b8..bc79314922 100644 --- a/redirect.go +++ b/redirect.go @@ -141,7 +141,7 @@ func (r *Redirect) With(key, value string, level ...uint8) *Redirect { // You can get them by using: Redirect().OldInputs(), Redirect().OldInput() func (r *Redirect) WithInput() *Redirect { // Get content-type - ctype := utils.ToLower(utils.UnsafeString(r.c.Context().Request.Header.ContentType())) + ctype := utils.ToLower(utils.UnsafeString(r.c.RequestCtx().Request.Header.ContentType())) ctype = binder.FilterFlags(utils.ParseVendorSpecificContentType(ctype)) oldInput := make(map[string]string) diff --git a/redirect_test.go b/redirect_test.go index 7544aec01d..1570d05fbf 100644 --- a/redirect_test.go +++ b/redirect_test.go @@ -42,7 +42,7 @@ func Test_Redirect_To_WithFlashMessages(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -185,7 +185,7 @@ func Test_Redirect_Back_WithFlashMessages(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "/", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -236,7 +236,7 @@ func Test_Redirect_Route_WithFlashMessages(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -273,7 +273,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -309,7 +309,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -353,7 +353,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) { require.Equal(t, 302, c.Response().StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -538,7 +538,7 @@ func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) { require.Equal(b, 302, c.Response().StatusCode()) require.Equal(b, "/user", string(c.Response().Header.Peek(HeaderLocation))) - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err = msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName))) @@ -629,7 +629,7 @@ func Benchmark_Redirect_processFlashMessages(b *testing.B) { c.Redirect().processFlashMessages() } - c.Context().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing + c.RequestCtx().Request.Header.Set(HeaderCookie, c.GetRespHeader(HeaderSetCookie)) // necessary for testing var msgs redirectionMsgs _, err := msgs.UnmarshalMsg([]byte(c.Cookies(FlashCookieName)))