diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index c8d7ee44..0dd0526c 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -71,11 +71,11 @@ Community leaders will follow these Community Impact Guidelines in determining t
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
-available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
-https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
+[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e35ce199..1ebcef7b 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -5,9 +5,11 @@ Before making any changes to this repository, we kindly request you to initiate
Please note: we have a [code of conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md), please follow it in all your interactions with the `Fiber` project.
## Pull Requests or Commits
+
Titles always we must use prefix according to below:
> 🔥 Feature, ♻️ Refactor, 🩹 Fix, 🚨 Test, 📚 Doc, 🎨 Style
+
- 🔥 Feature: Add flow to add person
- ♻️ Refactor: Rename file X to Y
- 🩹 Fix: Improve flow
@@ -17,7 +19,7 @@ Titles always we must use prefix according to below:
All pull requests that contain a feature or fix are mandatory to have unit tests. Your PR is only to be merged if you respect this flow.
-# 👍 Contribute
+## 👍 Contribute
If you want to say **thank you** and/or support the active development of `Fiber`:
diff --git a/.github/README.md b/.github/README.md
index a7cc2f75..68fbd9c9 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,4 +1,4 @@
-
Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development with zero memory allocation and performance in mind.
@@ -39,7 +39,7 @@ Fiber v3 is currently in beta and under active development. While it offers exci
## ⚙️ Installation
-Fiber requires **Go version `1.21` 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
@@ -57,7 +57,7 @@ This command fetches the Fiber package and adds it to your project's dependencie
## ⚡️ Quickstart
-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.
+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
package main
@@ -100,19 +100,19 @@ These tests are performed by [TechEmpower](https://www.techempower.com/benchmark
## 🎯 Features
-- Robust [Routing](https://docs.gofiber.io/guide/routing)
-- Serve [Static Files](https://docs.gofiber.io/api/app#static)
-- Extreme [Performance](https://docs.gofiber.io/extra/benchmarks)
-- [Low Memory](https://docs.gofiber.io/extra/benchmarks) footprint
-- [API Endpoints](https://docs.gofiber.io/api/ctx)
-- [Middleware](https://docs.gofiber.io/category/-middleware) & [Next](https://docs.gofiber.io/api/ctx#next) support
-- [Rapid](https://dev.to/koddr/welcome-to-fiber-an-express-js-styled-fastest-web-framework-written-with-on-golang-497) server-side programming
-- [Template Engines](https://github.com/gofiber/template)
-- [WebSocket Support](https://github.com/gofiber/contrib/tree/main/websocket)
-- [Socket.io Support](https://github.com/gofiber/contrib/tree/main/socketio)
-- [Server-Sent Events](https://github.com/gofiber/recipes/tree/master/sse)
-- [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter)
-- And much more, [explore Fiber](https://docs.gofiber.io/)
+- Robust [Routing](https://docs.gofiber.io/guide/routing)
+- Serve [Static Files](https://docs.gofiber.io/api/app#static)
+- Extreme [Performance](https://docs.gofiber.io/extra/benchmarks)
+- [Low Memory](https://docs.gofiber.io/extra/benchmarks) footprint
+- [API Endpoints](https://docs.gofiber.io/api/ctx)
+- [Middleware](https://docs.gofiber.io/category/-middleware) & [Next](https://docs.gofiber.io/api/ctx#next) support
+- [Rapid](https://dev.to/koddr/welcome-to-fiber-an-express-js-styled-fastest-web-framework-written-with-on-golang-497) server-side programming
+- [Template Engines](https://github.com/gofiber/template)
+- [WebSocket Support](https://github.com/gofiber/contrib/tree/main/websocket)
+- [Socket.io Support](https://github.com/gofiber/contrib/tree/main/socketio)
+- [Server-Sent Events](https://github.com/gofiber/recipes/tree/master/sse)
+- [Rate Limiter](https://docs.gofiber.io/api/middleware/limiter)
+- And much more, [explore Fiber](https://docs.gofiber.io/)
## 💡 Philosophy
@@ -124,14 +124,14 @@ We **listen** to our users in [issues](https://github.com/gofiber/fiber/issues),
## ⚠️ Limitations
-- Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber v3 has been tested with Go versions 1.21 and 1.22.
-- Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem.
+- Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber v3 has been tested with Go versions 1.22 and 1.23.
+- Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem.
## 👀 Examples
Listed below are some of the common examples. If you want to see more code examples, please visit our [Recipes repository](https://github.com/gofiber/recipes) or visit our hosted [API documentation](https://docs.gofiber.io).
-#### 📖 [**Basic Routing**](https://docs.gofiber.io/#basic-routing)
+### 📖 [**Basic Routing**](https://docs.gofiber.io/#basic-routing)
```go
func main() {
@@ -615,7 +615,7 @@ List of externally hosted middleware modules and maintained by the [Fiber team](
| :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- |
| [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.21 or higher is required. |
+| [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
@@ -634,14 +634,14 @@ If you want to say **Thank You** and/or support the active development of `Fiber
To ensure your contributions are ready for a Pull Request, please use the following `Makefile` commands. These tools help maintain code quality, consistency.
-* **make help**: Display available commands.
-* **make audit**: Conduct quality checks.
-* **make benchmark**: Benchmark code performance.
-* **make coverage**: Generate test coverage report.
-* **make format**: Automatically format code.
-* **make lint**: Run lint checks.
-* **make test**: Execute all tests.
-* **make tidy**: Tidy dependencies.
+- **make help**: Display available commands.
+- **make audit**: Conduct quality checks.
+- **make benchmark**: Benchmark code performance.
+- **make coverage**: Generate test coverage report.
+- **make format**: Automatically format code.
+- **make lint**: Run lint checks.
+- **make test**: Execute all tests.
+- **make tidy**: Tidy dependencies.
Run these commands to ensure your code adheres to project standards and best practices.
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 9d4826fe..a5919f86 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -6,6 +6,7 @@
4. [Incident Response Process](#process)
+
## Supported Versions
The table below shows the supported versions for Fiber which include security updates.
@@ -16,6 +17,7 @@ The table below shows the supported versions for Fiber which include security up
| < 1.12.6 | :x: |
+
## Reporting security problems to Fiber
**DO NOT CREATE AN ISSUE** to report a security problem. Instead, please
@@ -24,6 +26,7 @@ send us an e-mail at `team@gofiber.io` or join our discord server via
to Fenny or any of the maintainers.
+
## Security Point of Contact
The security point of contact is [Fenny](https://github.com/Fenny). Fenny responds
@@ -35,6 +38,7 @@ of contact are any of the [@maintainers](https://github.com/orgs/gofiber/teams/m
The maintainers are the only other persons with administrative access to Fiber's source code.
+
## Incident Response Process
In case an incident is discovered or reported, we will follow the following
@@ -73,4 +77,4 @@ for all of it's members.
We learn about critical software updates and security threats from these sources
1. GitHub Security Alerts
-2. GitHub: https://status.github.com/ & [@githubstatus](https://twitter.com/githubstatus)
+2. GitHub: [https://status.github.com/](https://status.github.com/) & [@githubstatus](https://twitter.com/githubstatus)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 073b9603..c1382990 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,4 +1,4 @@
-## Description
+# Description
Please provide a clear and concise description of the changes you've made and the problem they address. Include the purpose of the change, any relevant issues it solves, and the benefits it brings to the project. If this change introduces new features or adjustments, highlight them here.
diff --git a/.github/workflows/auto-labeler.yml b/.github/workflows/auto-labeler.yml
index b0fcc599..7b2cdac1 100644
--- a/.github/workflows/auto-labeler.yml
+++ b/.github/workflows/auto-labeler.yml
@@ -5,6 +5,7 @@ on:
types: [opened, edited, milestoned]
pull_request_target:
types: [opened]
+
permissions:
contents: read
issues: write
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 4a24c230..e8531e1c 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -3,15 +3,19 @@ on:
branches:
- master
- main
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
pull_request:
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
+
+permissions:
+ # deployments permission to deploy GitHub pages website
+ deployments: write
+ # contents permission to update benchmark contents in gh-pages branch
+ contents: write
+ # allow posting comments to pull request
+ pull-requests: write
name: Benchmark
jobs:
@@ -20,34 +24,88 @@ jobs:
steps:
- name: Fetch Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # to be able to retrieve the last commit in main
- name: Install Go
uses: actions/setup-go@v5
with:
# NOTE: Keep this in sync with the version from go.mod
- go-version: "1.21.x"
+ go-version: "1.22.x"
- name: Run Benchmark
run: set -o pipefail; go test ./... -benchmem -run=^$ -bench . | tee output.txt
- - name: Get Previous Benchmark Results
- uses: actions/cache@v4
+ # NOTE: Benchmarks could change with different CPU types
+ - name: Get GitHub Runner System Information
+ uses: kenchan0130/actions-system-info@v1.3.0
+ id: system-info
+
+ - name: Get Main branch SHA
+ id: get-main-branch-sha
+ run: |
+ SHA=$(git rev-parse origin/main)
+ echo "sha=$SHA" >> $GITHUB_OUTPUT
+
+ - name: Get Benchmark Results from main branch
+ id: cache
+ uses: actions/cache/restore@v4
with:
path: ./cache
- key: ${{ runner.os }}-benchmark
+ key: ${{ steps.get-main-branch-sha.outputs.sha }}-${{ runner.os }}-${{ steps.system-info.outputs.cpu-model }}-benchmark
- - name: Save Benchmark Results
+ # This will only run if we have Benchmark Results from main branch
+ - name: Compare PR Benchmark Results with main branch
uses: benchmark-action/github-action-benchmark@v1.20.3
+ if: steps.cache.outputs.cache-hit == 'true'
with:
- tool: "go"
+ tool: 'go'
output-file-path: output.txt
- github-token: ${{ secrets.BENCHMARK_TOKEN }}
- benchmark-data-dir-path: "benchmarks"
+ external-data-json-path: ./cache/benchmark-data.json
+ # Do not save the data (This allows comparing benchmarks)
+ save-data-file: false
fail-on-alert: true
- comment-on-alert: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
- # Enable Job Summary for PRs - deactivated because of issues
- #summary-always: ${{ github.event_name != 'push' && github.event_name != 'workflow_dispatch' }}
+ # Comment on the PR if the branch is not a fork
+ comment-on-alert: ${{ github.event.pull_request.head.repo.fork == false }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ summary-always: true
+ alert-threshold: "150%"
+
+ - name: Store Benchmark Results for main branch
+ uses: benchmark-action/github-action-benchmark@v1.20.3
+ if: ${{ github.ref_name == 'main' }}
+ with:
+ tool: 'go'
+ output-file-path: output.txt
+ external-data-json-path: ./cache/benchmark-data.json
+ # Save the data to external file (cache)
+ save-data-file: true
+ fail-on-alert: false
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ summary-always: true
+ alert-threshold: "150%"
+
+ - name: Publish Benchmark Results to GitHub Pages
+ uses: benchmark-action/github-action-benchmark@v1.20.3
+ if: ${{ github.ref_name == 'main' }}
+ with:
+ tool: 'go'
+ output-file-path: output.txt
+ benchmark-data-dir-path: "benchmarks"
+ fail-on-alert: false
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ comment-on-alert: true
+ summary-always: true
+ # Save the data to external file (GitHub Pages)
+ save-data-file: true
+ alert-threshold: "150%"
# TODO: reactivate it later -> when v3 is the stable one
#auto-push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
auto-push: false
- save-data-file: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
+
+ - name: Update Benchmark Results cache
+ uses: actions/cache/save@v4
+ if: ${{ github.ref_name == 'main' }}
+ with:
+ path: ./cache
+ key: ${{ steps.get-main-branch-sha.outputs.sha }}-${{ runner.os }}-${{ steps.system-info.outputs.cpu-model }}-benchmark
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 82036cc9..b145d1cd 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -5,15 +5,11 @@ on:
branches:
- master
- main
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
pull_request:
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
schedule:
- cron: "0 3 * * 6"
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 67a37b44..6c378e40 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -6,7 +6,11 @@ on:
branches:
- master
- main
+ paths-ignore:
+ - "**/*.md"
pull_request:
+ paths-ignore:
+ - "**/*.md"
permissions:
# Required: allow read access to the content for analysis.
@@ -26,11 +30,11 @@ jobs:
- uses: actions/setup-go@v5
with:
# NOTE: Keep this in sync with the version from go.mod
- go-version: "1.21.x"
+ go-version: "1.22.x"
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
# NOTE: Keep this in sync with the version from .golangci.yml
- version: v1.59.1
+ version: v1.60.3
diff --git a/.github/workflows/manual-dependabot.yml b/.github/workflows/manual-dependabot.yml
new file mode 100644
index 00000000..41e06303
--- /dev/null
+++ b/.github/workflows/manual-dependabot.yml
@@ -0,0 +1,46 @@
+# https://github.com/dependabot/dependabot-script/blob/main/manual-github-actions.yaml
+# https://github.com/dependabot/dependabot-script?tab=readme-ov-file#github-actions-standalone
+name: ManualDependabot
+
+on:
+ workflow_dispatch:
+ inputs:
+ package-manager:
+ description: 'The package manager to use'
+ required: true
+ default: 'gomod'
+ directory:
+ description: 'The directory to scan'
+ required: true
+ default: '/'
+
+permissions:
+ contents: read
+
+jobs:
+ dependabot:
+ permissions:
+ contents: write # for Git to git push
+ pull-requests: write # for repo-sync/pull-request to create pull requests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Checkout dependabot
+ run: |
+ cd /tmp/
+ git clone https://github.com/dependabot/dependabot-script
+
+ - name: Build image
+ run: |
+ cd /tmp/dependabot-script
+ docker build -t "dependabot/dependabot-script" -f Dockerfile .
+
+ - name: Run dependabot
+ env:
+ PACKAGE_MANAGER: ${{ github.event.inputs.package-manager }}
+ DIRECTORY: ${{ github.event.inputs.directory }}
+ GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ docker run -v $PWD:/src -e PROJECT_PATH=$GITHUB_REPOSITORY -e PACKAGE_MANAGER=$PACKAGE_MANAGER -e DIRECTORY=$DIRECTORY -e GITHUB_ACCESS_TOKEN=$GITHUB_ACCESS_TOKEN -e OPTIONS="$OPTIONS" dependabot/dependabot-script
diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml
new file mode 100644
index 00000000..cc131e7f
--- /dev/null
+++ b/.github/workflows/markdown.yml
@@ -0,0 +1,22 @@
+name: markdownlint
+
+on:
+ push:
+ branches:
+ - master
+ - main
+ pull_request:
+
+jobs:
+ markdownlint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Fetch Repository
+ uses: actions/checkout@v4
+
+ - name: Run markdownlint-cli2
+ uses: DavidAnson/markdownlint-cli2-action@v16
+ with:
+ globs: |
+ **/*.md
+ #vendor
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e6706493..f7770993 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,22 +5,18 @@ on:
branches:
- master
- main
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
pull_request:
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
jobs:
unit:
strategy:
matrix:
- go-version: [1.21.x, 1.22.x]
- platform: [ubuntu-latest, windows-latest, macos-latest, macos-14]
+ go-version: [1.22.x, 1.23.x]
+ platform: [ubuntu-latest, windows-latest, macos-latest, macos-13]
runs-on: ${{ matrix.platform }}
steps:
- name: Fetch Repository
@@ -35,7 +31,7 @@ jobs:
run: go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=1 -coverprofile=coverage.txt -covermode=atomic -shuffle=on
- name: Upload coverage reports to Codecov
- if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.22.x' }}
+ if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }}
uses: codecov/codecov-action@v4.5.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml
index e6d29ccb..31c252eb 100644
--- a/.github/workflows/vulncheck.yml
+++ b/.github/workflows/vulncheck.yml
@@ -5,15 +5,11 @@ on:
branches:
- master
- main
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
pull_request:
- paths:
- - "**"
- - "!docs/**"
- - "!**.md"
+ paths-ignore:
+ - "**/*.md"
jobs:
govulncheck-check:
diff --git a/.golangci.yml b/.golangci.yml
index f5e43b50..8b8d27b8 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -101,7 +101,6 @@ linters-settings:
govet:
enable-all: true
disable:
- - fieldalignment
- shadow
grouper:
@@ -279,7 +278,7 @@ linters:
- depguard
- dogsled
# - dupl
- # - dupword # TODO: Enable
+ - dupword # TODO: Enable
- durationcheck
- errcheck
- errchkjson
@@ -288,7 +287,7 @@ linters:
- exhaustive
# - exhaustivestruct
# - exhaustruct
- - exportloopref
+ - copyloopvar
- forbidigo
- forcetypeassert
# - funlen
@@ -299,7 +298,7 @@ linters:
# - gochecknoinits # TODO: Enable
- gochecksumtype
# - gocognit
- # - goconst # TODO: Enable
+ - goconst # TODO: Enable
- gocritic
# - gocyclo
# - godot
@@ -308,9 +307,8 @@ linters:
- gofmt
- gofumpt
# - goheader
- # - goimports
- # - golint
- # - gomnd # TODO: Enable
+ - goimports
+ # - mnd # TODO: Enable
- gomoddirectives
# - gomodguard
- goprintffuncname
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 00000000..71d7b309
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,248 @@
+# Example markdownlint configuration with all properties set to their default value
+
+# Default state for all rules
+default: true
+
+# Path to configuration file to extend
+extends: null
+
+# MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md001.md
+MD001: true
+
+# MD003/heading-style : Heading style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md003.md
+MD003:
+ # Heading style
+ style: "consistent"
+
+# MD004/ul-style : Unordered list style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md004.md
+MD004:
+ # List style
+ style: "consistent"
+
+# MD005/list-indent : Inconsistent indentation for list items at the same level : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md005.md
+MD005: true
+
+# MD007/ul-indent : Unordered list indentation : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md007.md
+MD007:
+ # Spaces for indent
+ indent:
+ # Whether to indent the first level of the list
+ start_indented: false
+ # Spaces for first level indent (when start_indented is set)
+ start_indent: 2
+
+# MD009/no-trailing-spaces : Trailing spaces : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md009.md
+MD009:
+ # Spaces for line break
+ br_spaces: 2
+ # Allow spaces for empty lines in list items
+ list_item_empty_lines: false
+ # Include unnecessary breaks
+ strict: true
+
+# MD010/no-hard-tabs : Hard tabs : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md010.md
+MD010:
+ # Include code blocks
+ code_blocks: true
+ # Fenced code languages to ignore
+ ignore_code_languages: []
+ # Number of spaces for each hard tab
+ spaces_per_tab: 4
+
+# MD011/no-reversed-links : Reversed link syntax : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md011.md
+MD011: true
+
+# MD012/no-multiple-blanks : Multiple consecutive blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md012.md
+MD012:
+ # Consecutive blank lines
+ maximum: 1
+
+# MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md
+MD013: false
+
+# MD014/commands-show-output : Dollar signs used before commands without showing output : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md014.md
+MD014: true
+
+# MD018/no-missing-space-atx : No space after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md018.md
+MD018: true
+
+# MD019/no-multiple-space-atx : Multiple spaces after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md019.md
+MD019: true
+
+# MD020/no-missing-space-closed-atx : No space inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md020.md
+MD020: true
+
+# MD021/no-multiple-space-closed-atx : Multiple spaces inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md021.md
+MD021: true
+
+# MD022/blanks-around-headings : Headings should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md
+MD022:
+ # Blank lines above heading
+ lines_above: 1
+ # Blank lines below heading
+ lines_below: 1
+
+# MD023/heading-start-left : Headings must start at the beginning of the line : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md023.md
+MD023: true
+
+# MD024/no-duplicate-heading : Multiple headings with the same content : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md024.md
+MD024: false
+
+# MD025/single-title/single-h1 : Multiple top-level headings in the same document : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md025.md
+MD025:
+ # Heading level
+ level: 1
+ # RegExp for matching title in front matter
+ front_matter_title: "^\\s*title\\s*[:=]"
+
+# MD026/no-trailing-punctuation : Trailing punctuation in heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md026.md
+MD026:
+ # Punctuation characters
+ punctuation: ".,;:!。,;:!"
+
+# MD027/no-multiple-space-blockquote : Multiple spaces after blockquote symbol : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md027.md
+MD027: true
+
+# MD028/no-blanks-blockquote : Blank line inside blockquote : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md028.md
+MD028: true
+
+# MD029/ol-prefix : Ordered list item prefix : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md029.md
+MD029:
+ # List style
+ style: "one_or_ordered"
+
+# MD030/list-marker-space : Spaces after list markers : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md030.md
+MD030:
+ # Spaces for single-line unordered list items
+ ul_single: 1
+ # Spaces for single-line ordered list items
+ ol_single: 1
+ # Spaces for multi-line unordered list items
+ ul_multi: 1
+ # Spaces for multi-line ordered list items
+ ol_multi: 1
+
+# MD031/blanks-around-fences : Fenced code blocks should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md031.md
+MD031:
+ # Include list items
+ list_items: true
+
+# MD032/blanks-around-lists : Lists should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md032.md
+MD032: true
+
+# MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md033.md
+MD033: false
+
+# MD034/no-bare-urls : Bare URL used : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md034.md
+MD034: true
+
+# MD035/hr-style : Horizontal rule style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md035.md
+MD035:
+ # Horizontal rule style
+ style: "consistent"
+
+# MD036/no-emphasis-as-heading : Emphasis used instead of a heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md036.md
+MD036:
+ # Punctuation characters
+ punctuation: ".,;:!?。,;:!?"
+
+# MD037/no-space-in-emphasis : Spaces inside emphasis markers : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md037.md
+MD037: true
+
+# MD038/no-space-in-code : Spaces inside code span elements : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md038.md
+MD038: true
+
+# MD039/no-space-in-links : Spaces inside link text : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md039.md
+MD039: true
+
+# MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md040.md
+MD040:
+ # List of languages
+ allowed_languages: []
+ # Require language only
+ language_only: false
+
+# MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md041.md
+MD041:
+ # Heading level
+ level: 1
+ # RegExp for matching title in front matter
+ front_matter_title: "^\\s*title\\s*[:=]"
+
+# MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md042.md
+MD042: true
+
+# MD043/required-headings : Required heading structure : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md043.md
+MD043: false
+
+# MD044/proper-names : Proper names should have the correct capitalization : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md044.md
+MD044:
+ # List of proper names
+ names: []
+ # Include code blocks
+ code_blocks: true
+ # Include HTML elements
+ html_elements: true
+
+# MD045/no-alt-text : Images should have alternate text (alt text) : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md045.md
+MD045: false
+
+# MD046/code-block-style : Code block style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md046.md
+MD046:
+ # Block style
+ style: "fenced"
+
+# MD047/single-trailing-newline : Files should end with a single newline character : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md047.md
+MD047: true
+
+# MD048/code-fence-style : Code fence style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md048.md
+MD048:
+ # Code fence style
+ style: "backtick"
+
+# MD049/emphasis-style : Emphasis style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md049.md
+MD049:
+ # Emphasis style
+ style: "consistent"
+
+# MD050/strong-style : Strong style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md050.md
+MD050:
+ # Strong style
+ style: "consistent"
+
+# MD051/link-fragments : Link fragments should be valid : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md051.md
+MD051: true
+
+# MD052/reference-links-images : Reference links and images should use a label that is defined : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md052.md
+MD052:
+ # Include shortcut syntax
+ shortcut_syntax: false
+
+# MD053/link-image-reference-definitions : Link and image reference definitions should be needed : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md053.md
+MD053:
+ # Ignored definitions
+ ignored_definitions:
+ - "//"
+
+# MD054/link-image-style : Link and image style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md054.md
+MD054:
+ # Allow autolinks
+ autolink: false
+ # Allow inline links and images
+ inline: true
+ # Allow full reference links and images
+ full: true
+ # Allow collapsed reference links and images
+ collapsed: true
+ # Allow shortcut reference links and images
+ shortcut: true
+ # Allow URLs as inline links
+ url_inline: true
+
+# MD055/table-pipe-style : Table pipe style : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md055.md
+MD055:
+ # Table pipe style
+ style: "consistent"
+
+# MD056/table-column-count : Table column count : https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md056.md
+MD056: true
diff --git a/Makefile b/Makefile
index 0d4be85f..1d7f57d0 100644
--- a/Makefile
+++ b/Makefile
@@ -27,10 +27,15 @@ coverage:
format:
go run mvdan.cc/gofumpt@latest -w -l .
+## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli)
+.PHONY: markdown
+markdown:
+ markdownlint-cli2 "**/*.md" "#vendor"
+
## lint: 🚨 Run lint checks
.PHONY: lint
lint:
- go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 run ./...
+ go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 run ./...
## test: 🚦 Execute all tests
.PHONY: test
@@ -46,3 +51,15 @@ longtest:
.PHONY: tidy
tidy:
go mod tidy -v
+
+## betteralign: 📐 Optimize alignment of fields in structs
+.PHONY: betteralign
+betteralign:
+ go run github.com/dkorunic/betteralign/cmd/betteralign@latest -test_files -generated_files -apply ./...
+
+## generate: ⚡️ Generate msgp && interface implementations
+.PHONY: generate
+generate:
+ go install github.com/tinylib/msgp@latest
+ go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c
+ go generate ./...
diff --git a/addon/retry/README.md b/addon/retry/README.md
index a07c7637..a3af4407 100644
--- a/addon/retry/README.md
+++ b/addon/retry/README.md
@@ -1,20 +1,20 @@
# Retry Addon
Retry addon for [Fiber](https://github.com/gofiber/fiber) designed to apply retry mechanism for unsuccessful network
-operations. This addon uses exponential backoff algorithm with jitter. It calls the function multiple times and tries
-to make it successful. If all calls are failed, then, it returns error. It adds a jitter at each retry step because adding
-a jitter is a way to break synchronization across the client and avoid collision.
+operations. This addon uses an exponential backoff algorithm with jitter. It calls the function multiple times and tries
+to make it successful. If all calls are failed, then, it returns an error. It adds a jitter at each retry step because adding
+a jitter is a way to break synchronization across the client and avoid collision.
## Table of Contents
- [Retry Addon](#retry-addon)
- - [Table of Contents](#table-of-contents)
- - [Signatures](#signatures)
- - [Examples](#examples)
- - [Default Config](#default-config)
- - [Custom Config](#custom-config)
- - [Config](#config)
- - [Default Config Example](#default-config-example)
+- [Table of Contents](#table-of-contents)
+- [Signatures](#signatures)
+- [Examples](#examples)
+- [Default Config](#default-config)
+- [Custom Config](#custom-config)
+- [Config](#config)
+- [Default Config Example](#default-config-example)
## Signatures
@@ -42,10 +42,10 @@ retry.NewExponentialBackoff()
```go
retry.NewExponentialBackoff(retry.Config{
- InitialInterval: 2 * time.Second,
- MaxBackoffTime: 64 * time.Second,
- Multiplier: 2.0,
- MaxRetryCount: 15,
+ InitialInterval: 2 * time.Second,
+ MaxBackoffTime: 64 * time.Second,
+ Multiplier: 2.0,
+ MaxRetryCount: 15,
})
```
@@ -88,10 +88,10 @@ type Config struct {
```go
// DefaultConfig is the default config for retry.
var DefaultConfig = Config{
- InitialInterval: 1 * time.Second,
- MaxBackoffTime: 32 * time.Second,
- Multiplier: 2.0,
- MaxRetryCount: 10,
- currentInterval: 1 * time.Second,
+ InitialInterval: 1 * time.Second,
+ MaxBackoffTime: 32 * time.Second,
+ Multiplier: 2.0,
+ MaxRetryCount: 10,
+ currentInterval: 1 * time.Second,
}
-```
\ No newline at end of file
+```
diff --git a/addon/retry/exponential_backoff_test.go b/addon/retry/exponential_backoff_test.go
index 0961d4fa..844ed0df 100644
--- a/addon/retry/exponential_backoff_test.go
+++ b/addon/retry/exponential_backoff_test.go
@@ -11,10 +11,10 @@ import (
func Test_ExponentialBackoff_Retry(t *testing.T) {
t.Parallel()
tests := []struct {
- name string
+ expErr error
expBackoff *ExponentialBackoff
f func() error
- expErr error
+ name string
}{
{
name: "With default values - successful",
@@ -51,7 +51,6 @@ func Test_ExponentialBackoff_Retry(t *testing.T) {
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.expBackoff.Retry(tt.f)
@@ -106,7 +105,6 @@ func Test_ExponentialBackoff_Next(t *testing.T) {
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
for i := 0; i < tt.expBackoff.MaxRetryCount; i++ {
diff --git a/app.go b/app.go
index 293f29ba..e0240d3c 100644
--- a/app.go
+++ b/app.go
@@ -80,29 +80,16 @@ type ErrorHandler = func(Ctx, error) error
// Error represents an error that occurred while handling a request.
type Error struct {
- Code int `json:"code"`
Message string `json:"message"`
+ Code int `json:"code"`
}
// App denotes the Fiber application.
type App struct {
- mutex sync.Mutex
- // Route stack divided by HTTP methods
- stack [][]*Route
- // Route stack divided by HTTP methods and route prefixes
- treeStack []map[string][]*Route
- // contains the information if the route stack has been changed to build the optimized tree
- routesRefreshed bool
- // Amount of registered routes
- routesCount uint32
- // Amount of registered handlers
- handlersCount uint32
// Ctx pool
pool sync.Pool
// Fasthttp server
server *fasthttp.Server
- // App config
- config Config
// Converts string to a byte slice
getBytes func(s string) (b []byte)
// Converts byte slice to a string
@@ -113,24 +100,37 @@ type App struct {
latestRoute *Route
// newCtxFunc
newCtxFunc func(app *App) CustomCtx
- // custom binders
- customBinders []CustomBinder
// TLS handler
tlsHandler *TLSHandler
// Mount fields
mountFields *mountFields
- // Indicates if the value was explicitly configured
- configured Config
+ // Route stack divided by HTTP methods
+ stack [][]*Route
+ // Route stack divided by HTTP methods and route prefixes
+ treeStack []map[string][]*Route
+ // custom binders
+ customBinders []CustomBinder
// customConstraints is a list of external constraints
customConstraints []CustomConstraint
// sendfiles stores configurations for handling ctx.SendFile operations
sendfiles []*sendFileStore
+ // App config
+ config Config
+ // Indicates if the value was explicitly configured
+ configured Config
// sendfilesMutex is a mutex used for sendfile operations
sendfilesMutex sync.RWMutex
+ mutex sync.Mutex
+ // Amount of registered routes
+ routesCount uint32
+ // Amount of registered handlers
+ handlersCount uint32
+ // contains the information if the route stack has been changed to build the optimized tree
+ routesRefreshed bool
}
// Config is a struct holding the server settings.
-type Config struct {
+type Config struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore
// Enables the "Server: value" HTTP header.
//
// Default: ""
diff --git a/bind_test.go b/bind_test.go
index 9b291451..48f53f62 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -26,9 +26,9 @@ func Test_Bind_Query(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -53,14 +53,14 @@ func Test_Bind_Query(t *testing.T) {
require.Empty(t, empty.Hobby)
type Query2 struct {
- Bool bool
- ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
+ ID int
+ Bool bool
}
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
@@ -237,8 +237,8 @@ func Test_Bind_Query_Schema(t *testing.T) {
require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error())
type Node struct {
- Value int `query:"val,required"`
Next *Node `query:"next,required"`
+ Value int `query:"val,required"`
}
c.Request().URI().SetQueryString("val=1&next.val=3")
n := new(Node)
@@ -292,9 +292,9 @@ func Test_Bind_Header(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -318,14 +318,14 @@ func Test_Bind_Header(t *testing.T) {
require.Empty(t, empty.Hobby)
type Header2 struct {
- Bool bool
- ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
+ ID int
+ Bool bool
}
c.Request().Header.Add("id", "2")
@@ -502,8 +502,8 @@ func Test_Bind_Header_Schema(t *testing.T) {
require.Equal(t, "Nested.age is empty", c.Bind().Header(h2).Error())
type Node struct {
- Value int `header:"Val,required"`
Next *Node `header:"Next,required"`
+ Value int `header:"Val,required"`
}
c.Request().Header.Add("Val", "1")
c.Request().Header.Add("Next.Val", "3")
@@ -533,9 +533,9 @@ func Test_Bind_RespHeader(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -559,14 +559,14 @@ func Test_Bind_RespHeader(t *testing.T) {
require.Empty(t, empty.Hobby)
type Header2 struct {
- Bool bool
- ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
+ ID int
+ Bool bool
}
c.Response().Header.Add("id", "2")
@@ -635,9 +635,9 @@ func Benchmark_Bind_Query(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -708,9 +708,9 @@ func Benchmark_Bind_Query_Comma(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -732,9 +732,9 @@ func Benchmark_Bind_Header(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -782,9 +782,9 @@ func Benchmark_Bind_RespHeader(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -1252,9 +1252,9 @@ func Test_Bind_Cookie(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@@ -1278,14 +1278,14 @@ func Test_Bind_Cookie(t *testing.T) {
require.Empty(t, empty.Hobby)
type Cookie2 struct {
- Bool bool
- ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
+ ID int
+ Bool bool
}
c.Request().Header.SetCookie("id", "2")
@@ -1463,8 +1463,8 @@ func Test_Bind_Cookie_Schema(t *testing.T) {
require.Equal(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error())
type Node struct {
- Value int `cookie:"Val,required"`
Next *Node `cookie:"Next,required"`
+ Value int `cookie:"Val,required"`
}
c.Request().Header.SetCookie("Val", "1")
c.Request().Header.SetCookie("Next.Val", "3")
@@ -1495,9 +1495,9 @@ func Benchmark_Bind_Cookie(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct {
- ID int
Name string
Hobby []string
+ ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
diff --git a/binder/README.md b/binder/README.md
index d40cc7e5..676e1c9e 100644
--- a/binder/README.md
+++ b/binder/README.md
@@ -1,6 +1,7 @@
# Fiber Binders
-Binder is new request/response binding feature for Fiber. By aganist old Fiber parsers, it supports custom binder registration, struct validation, **map[string]string**, **map[string][]string** and more. It's introduced in Fiber v3 and a replacement of:
+Binder is a new request/response binding feature for Fiber. Against the old Fiber parsers, it supports custom binder registration, struct validation, `map[string]string`, `map[string][]string`, and more. It's introduced in Fiber v3 and a replacement of:
+
- BodyParser
- ParamsParser
- GetReqHeaders
@@ -9,8 +10,8 @@ Binder is new request/response binding feature for Fiber. By aganist old Fiber p
- QueryParser
- ReqHeaderParser
-
## Default Binders
+
- [Form](form.go)
- [Query](query.go)
- [URI](uri.go)
@@ -23,7 +24,9 @@ Binder is new request/response binding feature for Fiber. By aganist old Fiber p
## Guides
### Binding into the Struct
-Fiber supports binding into the struct with [gorilla/schema](https://github.com/gorilla/schema). Here's an example for it:
+
+Fiber supports binding into the struct with [gorilla/schema](https://github.com/gorilla/schema). Here's an example:
+
```go
// Field names should start with an uppercase letter
type Person struct {
@@ -32,16 +35,16 @@ type Person struct {
}
app.Post("/", func(c fiber.Ctx) error {
- p := new(Person)
+ p := new(Person)
- if err := c.Bind().Body(p); err != nil {
- return err
- }
+ if err := c.Bind().Body(p); err != nil {
+ return err
+ }
- log.Println(p.Name) // john
- log.Println(p.Pass) // doe
+ log.Println(p.Name) // john
+ log.Println(p.Pass) // doe
- // ...
+ // ...
})
// Run tests with the following curl commands:
@@ -58,29 +61,34 @@ app.Post("/", func(c fiber.Ctx) error {
```
### Binding into the Map
-Fiber supports binding into the **map[string]string** or **map[string][]string**. Here's an example for it:
+
+Fiber supports binding into the `map[string]string` or `map[string][]string`. Here's an example:
+
```go
app.Get("/", func(c fiber.Ctx) error {
- p := make(map[string][]string)
+ p := make(map[string][]string)
- if err := c.Bind().Query(p); err != nil {
- return err
- }
+ if err := c.Bind().Query(p); err != nil {
+ 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:
// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat"
```
+
### Behaviors of Should/Must
-Normally, Fiber returns binder error directly. However; if you want to handle it automatically, you can prefer `Must()`.
+
+Normally, Fiber returns binder error directly. However; if you want to handle it automatically, you can prefer `Must()`.
If there's an error it'll return error and 400 as HTTP status. Here's an example for it:
+
```go
// Field names should start with an uppercase letter
type Person struct {
@@ -89,106 +97,112 @@ type Person struct {
}
app.Get("/", func(c fiber.Ctx) error {
- p := new(Person)
+ p := new(Person)
- if err := c.Bind().Must().JSON(p); err != nil {
- return err
- // Status code: 400
- // Response: Bad request: name is empty
- }
+ if err := c.Bind().Must().JSON(p); err != nil {
+ return err
+ // Status code: 400
+ // Response: Bad request: name is empty
+ }
- // ...
+ // ...
})
// Run tests with the following curl command:
// curl -X GET -H "Content-Type: application/json" --data "{\"pass\":\"doe\"}" localhost:3000
```
+
### Defining Custom Binder
-We didn't add much binder to make Fiber codebase minimal. But if you want to use your binders, it's easy to register and use them. Here's an example for TOML binder.
+
+We didn't add much binder to make Fiber codebase minimal. If you want to use your own binders, it's easy to register and use them. Here's an example for TOML binder.
+
```go
type Person struct {
- Name string `toml:"name"`
- Pass string `toml:"pass"`
+ Name string `toml:"name"`
+ Pass string `toml:"pass"`
}
type tomlBinding struct{}
func (b *tomlBinding) Name() string {
- return "toml"
+ return "toml"
}
func (b *tomlBinding) MIMETypes() []string {
- return []string{"application/toml"}
+ return []string{"application/toml"}
}
func (b *tomlBinding) Parse(c fiber.Ctx, out any) error {
- return toml.Unmarshal(c.Body(), out)
+ return toml.Unmarshal(c.Body(), out)
}
func main() {
- app := fiber.New()
- app.RegisterCustomBinder(&tomlBinding{})
+ app := fiber.New()
+ app.RegisterCustomBinder(&tomlBinding{})
- app.Get("/", func(c fiber.Ctx) error {
- out := new(Person)
- if err := c.Bind().Body(out); err != nil {
- return err
- }
+ app.Get("/", func(c fiber.Ctx) error {
+ out := new(Person)
+ if err := c.Bind().Body(out); err != nil {
+ return err
+ }
- // or you can use like:
- // if err := c.Bind().Custom("toml", out); err != nil {
- // return err
- // }
+ // or you can use like:
+ // if err := c.Bind().Custom("toml", out); err != nil {
+ // return err
+ // }
- return c.SendString(out.Pass) // test
- })
+ return c.SendString(out.Pass) // test
+ })
- app.Listen(":3000")
+ app.Listen(":3000")
}
// curl -X GET -H "Content-Type: application/toml" --data "name = 'bar'
// pass = 'test'" localhost:3000
```
+
### Defining Custom Validator
+
All Fiber binders supporting struct validation if you defined validator inside of the config. You can create own validator, or use [go-playground/validator](https://github.com/go-playground/validator), [go-ozzo/ozzo-validation](https://github.com/go-ozzo/ozzo-validation)... Here's an example of simple custom validator:
+
```go
type Query struct {
- Name string `query:"name"`
+ Name string `query:"name"`
}
type structValidator struct{}
func (v *structValidator) Engine() any {
- return ""
+ return ""
}
func (v *structValidator) ValidateStruct(out any) error {
- out = reflect.ValueOf(out).Elem().Interface()
- sq := out.(Query)
+ out = reflect.ValueOf(out).Elem().Interface()
+ sq := out.(Query)
- if sq.Name != "john" {
- return errors.New("you should have entered right name!")
- }
+ if sq.Name != "john" {
+ return errors.New("you should have entered right name!")
+ }
- return nil
+ return nil
}
func main() {
- app := fiber.New(fiber.Config{StructValidator: &structValidator{}})
+ app := fiber.New(fiber.Config{StructValidator: &structValidator{}})
- app.Get("/", func(c fiber.Ctx) error {
- out := new(Query)
- if err := c.Bind().Query(out); err != nil {
- return err // you should have entered right name!
- }
- return c.SendString(out.Name)
- })
+ app.Get("/", func(c fiber.Ctx) error {
+ out := new(Query)
+ if err := c.Bind().Query(out); err != nil {
+ return err // you should have entered right name!
+ }
+ return c.SendString(out.Name)
+ })
- app.Listen(":3000")
+ app.Listen(":3000")
}
// Run tests with the following curl command:
// curl "http://localhost:3000/?name=efe"
-```
\ No newline at end of file
+```
diff --git a/binder/form.go b/binder/form.go
index 0b469086..f45407fe 100644
--- a/binder/form.go
+++ b/binder/form.go
@@ -40,6 +40,10 @@ func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error {
}
})
+ if err != nil {
+ return err
+ }
+
return parse(b.Name(), out, data)
}
diff --git a/binder/mapping.go b/binder/mapping.go
index 7a09140e..07af94a1 100644
--- a/binder/mapping.go
+++ b/binder/mapping.go
@@ -12,9 +12,9 @@ import (
// ParserConfig form decoder config for SetParserDecoder
type ParserConfig struct {
- IgnoreUnknownKeys bool
SetAliasTag string
ParserType []ParserType
+ IgnoreUnknownKeys bool
ZeroEmpty bool
}
diff --git a/client/client.go b/client/client.go
index e9c65e14..d9b9c84c 100644
--- a/client/client.go
+++ b/client/client.go
@@ -7,9 +7,7 @@ import (
"encoding/json"
"encoding/xml"
"errors"
- "fmt"
"io"
- urlpkg "net/url"
"os"
"path/filepath"
"sync"
@@ -20,12 +18,10 @@ import (
"github.com/gofiber/utils/v2"
"github.com/valyala/fasthttp"
+ "github.com/valyala/fasthttp/fasthttpproxy"
)
-var (
- ErrInvalidProxyURL = errors.New("invalid proxy url scheme")
- ErrFailedToAppendCert = errors.New("failed to append certificate")
-)
+var ErrFailedToAppendCert = errors.New("failed to append certificate")
// The Client is used to create a Fiber Client with
// client-level settings that apply to all requests
@@ -34,21 +30,29 @@ var (
// Fiber Client also provides an option to override
// or merge most of the client settings at the request.
type Client struct {
- mu sync.RWMutex
+ // logger
+ logger log.CommonLogger
fasthttp *fasthttp.Client
+ header *Header
+ params *QueryParam
+ cookies *Cookie
+ path *PathParam
+
+ jsonMarshal utils.JSONMarshal
+ jsonUnmarshal utils.JSONUnmarshal
+ xmlMarshal utils.XMLMarshal
+ xmlUnmarshal utils.XMLUnmarshal
+
+ cookieJar *CookieJar
+
+ // retry
+ retryConfig *RetryConfig
+
baseURL string
userAgent string
referer string
- header *Header
- params *QueryParam
- cookies *Cookie
- path *PathParam
-
- debug bool
-
- timeout time.Duration
// user defined request hooks
userRequestHooks []RequestHook
@@ -62,21 +66,11 @@ type Client struct {
// client package defined response hooks
builtinResponseHooks []ResponseHook
- jsonMarshal utils.JSONMarshal
- jsonUnmarshal utils.JSONUnmarshal
- xmlMarshal utils.XMLMarshal
- xmlUnmarshal utils.XMLUnmarshal
+ timeout time.Duration
- cookieJar *CookieJar
+ mu sync.RWMutex
- // proxy
- proxyURL string
-
- // retry
- retryConfig *RetryConfig
-
- // logger
- logger log.CommonLogger
+ debug bool
}
// R raise a request from the client.
@@ -228,16 +222,7 @@ func (c *Client) SetRootCertificateFromString(pem string) *Client {
// SetProxyURL sets proxy url in client. It will apply via core to hostclient.
func (c *Client) SetProxyURL(proxyURL string) error {
- pURL, err := urlpkg.Parse(proxyURL)
- if err != nil {
- return fmt.Errorf("client: %w", err)
- }
-
- if pURL.Scheme != "http" && pURL.Scheme != "https" {
- return fmt.Errorf("client: %w", ErrInvalidProxyURL)
- }
-
- c.proxyURL = pURL.String()
+ c.fasthttp.Dial = fasthttpproxy.FasthttpHTTPDialer(proxyURL)
return nil
}
@@ -581,7 +566,6 @@ func (c *Client) Reset() {
c.timeout = 0
c.userAgent = ""
c.referer = ""
- c.proxyURL = ""
c.retryConfig = nil
c.debug = false
@@ -604,19 +588,20 @@ func (c *Client) Reset() {
type Config struct {
Ctx context.Context //nolint:containedctx // It's needed to be stored in the config.
- UserAgent string
- Referer string
+ Body any
Header map[string]string
Param map[string]string
Cookie map[string]string
PathParam map[string]string
+ FormData map[string]string
+
+ UserAgent string
+ Referer string
+ File []*File
+
Timeout time.Duration
MaxRedirects int
-
- Body any
- FormData map[string]string
- File []*File
}
// setConfigToRequest Set the parameters passed via Config to Request.
@@ -672,7 +657,7 @@ func setConfigToRequest(req *Request, config ...Config) {
return
}
- if cfg.File != nil && len(cfg.File) != 0 {
+ if len(cfg.File) != 0 {
req.AddFiles(cfg.File...)
return
}
diff --git a/client/client_test.go b/client/client_test.go
index 0f81dda9..b8dd39bb 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -19,6 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/bytebufferpool"
+ "github.com/valyala/fasthttp"
)
func startTestServerWithPort(t *testing.T, beforeStarting func(app *fiber.App)) (*fiber.App, string) {
@@ -835,8 +836,8 @@ func Test_Client_Cookie(t *testing.T) {
t.Run("set cookies with struct", func(t *testing.T) {
t.Parallel()
type args struct {
- CookieInt int `cookie:"int"`
CookieString string `cookie:"string"`
+ CookieInt int `cookie:"int"`
}
req := New().SetCookiesWithStruct(&args{
@@ -1087,12 +1088,12 @@ func Test_Client_QueryParam(t *testing.T) {
t.Parallel()
type args struct {
- TInt int
TString string
- TFloat float64
- TBool bool
TSlice []string
TIntSlice []int `param:"int_slice"`
+ TInt int
+ TFloat float64
+ TBool bool
}
p := New()
@@ -1195,8 +1196,8 @@ func Test_Client_PathParam(t *testing.T) {
t.Run("set path params with struct", func(t *testing.T) {
t.Parallel()
type args struct {
- CookieInt int `path:"int"`
CookieString string `path:"string"`
+ CookieInt int `path:"int"`
}
req := New().SetPathParamsWithStruct(&args{
@@ -1422,7 +1423,7 @@ func Test_Set_Config_To_Request(t *testing.T) {
key := struct{}{}
ctx := context.Background()
- ctx = context.WithValue(ctx, key, "v1")
+ ctx = context.WithValue(ctx, key, "v1") //nolint: staticcheck // not needed for tests
req := AcquireRequest()
@@ -1539,11 +1540,53 @@ func Test_Client_SetProxyURL(t *testing.T) {
app, dial, start := createHelperServer(t)
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString("hello world")
+ return c.SendString(c.Get("isProxy"))
})
go start()
+ fasthttpClient := &fasthttp.Client{
+ Dial: dial,
+ NoDefaultUserAgentHeader: true,
+ DisablePathNormalizing: true,
+ }
+
+ // Create a simple proxy sever
+ proxyServer := fiber.New()
+
+ proxyServer.Use("*", func(c fiber.Ctx) error {
+ req := fasthttp.AcquireRequest()
+ resp := fasthttp.AcquireResponse()
+
+ req.SetRequestURI(c.BaseURL())
+ req.Header.SetMethod(fasthttp.MethodGet)
+
+ c.Request().Header.VisitAll(func(key, value []byte) {
+ req.Header.AddBytesKV(key, value)
+ })
+
+ req.Header.Set("isProxy", "true")
+
+ if err := fasthttpClient.Do(req, resp); err != nil {
+ return err
+ }
+
+ c.Status(resp.StatusCode())
+ c.Context().SetBody(resp.Body())
+
+ return nil
+ })
+
+ addrChan := make(chan string)
+ go func() {
+ assert.NoError(t, proxyServer.Listen(":0", fiber.ListenConfig{
+ DisableStartupMessage: true,
+ ListenerAddrFunc: func(addr net.Addr) {
+ addrChan <- addr.String()
+ },
+ }))
+ }()
+
t.Cleanup(func() {
require.NoError(t, app.Shutdown())
})
@@ -1552,31 +1595,27 @@ func Test_Client_SetProxyURL(t *testing.T) {
t.Run("success", func(t *testing.T) {
t.Parallel()
- client := New().SetDial(dial)
- err := client.SetProxyURL("http://test.com")
- require.NoError(t, err)
-
- _, err = client.Get("http://localhost:3000")
-
- require.NoError(t, err)
- })
-
- t.Run("wrong url", func(t *testing.T) {
- t.Parallel()
client := New()
+ err := client.SetProxyURL(<-addrChan)
- err := client.SetProxyURL(":this is not a url")
+ require.NoError(t, err)
- require.Error(t, err)
+ resp, err := client.Get("http://localhost:3000")
+ require.NoError(t, err)
+
+ require.Equal(t, 200, resp.StatusCode())
+ require.Equal(t, "true", string(resp.Body()))
})
t.Run("error", func(t *testing.T) {
t.Parallel()
client := New()
- err := client.SetProxyURL("htgdftp://test.com")
+ err := client.SetProxyURL(":this is not a proxy")
+ require.NoError(t, err)
+ _, err = client.Get("http://localhost:3000")
require.Error(t, err)
})
}
diff --git a/client/cookiejar.go b/client/cookiejar.go
index c66d5f3b..834357fb 100644
--- a/client/cookiejar.go
+++ b/client/cookiejar.go
@@ -36,8 +36,8 @@ func ReleaseCookieJar(c *CookieJar) {
// CookieJar manages cookie storage. It is used by the client to store cookies.
type CookieJar struct {
- mu sync.Mutex
hostCookies map[string][]*fasthttp.Cookie
+ mu sync.Mutex
}
// Get returns the cookies stored from a specific domain.
diff --git a/client/core_test.go b/client/core_test.go
index c985784c..36e8eb78 100644
--- a/client/core_test.go
+++ b/client/core_test.go
@@ -22,8 +22,8 @@ func Test_AddMissing_Port(t *testing.T) {
}
tests := []struct {
name string
- args args
want string
+ args args
}{
{
name: "do anything",
@@ -49,7 +49,6 @@ func Test_AddMissing_Port(t *testing.T) {
},
}
for _, tt := range tests {
- tt := tt // create a new 'tt' variable for the goroutine
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, addMissingPort(tt.args.addr, tt.args.isTLS))
diff --git a/client/hooks_test.go b/client/hooks_test.go
index dfea361d..359e3184 100644
--- a/client/hooks_test.go
+++ b/client/hooks_test.go
@@ -35,7 +35,6 @@ func Test_Rand_String(t *testing.T) {
},
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := randString(tt.args)
@@ -188,7 +187,7 @@ func Test_Parser_Request_URL(t *testing.T) {
flag1 = true
case "foo2":
flag2 = true
- case "foo":
+ case "foo": //nolint:goconst // test
flag3 = true
}
}
diff --git a/client/request.go b/client/request.go
index ebddb0d0..61b5798c 100644
--- a/client/request.go
+++ b/client/request.go
@@ -40,28 +40,30 @@ var ErrClientNil = errors.New("client can not be nil")
// Request is a struct which contains the request data.
type Request struct {
- url string
- method string
- userAgent string
- boundary string
- referer string
- ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
- header *Header
- params *QueryParam
- cookies *Cookie
- path *PathParam
+ ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
+
+ body any
+ header *Header
+ params *QueryParam
+ cookies *Cookie
+ path *PathParam
+
+ client *Client
+
+ formData *FormData
+
+ RawRequest *fasthttp.Request
+ url string
+ method string
+ userAgent string
+ boundary string
+ referer string
+ files []*File
timeout time.Duration
maxRedirects int
- client *Client
-
- body any
- formData *FormData
- files []*File
bodyType bodyType
-
- RawRequest *fasthttp.Request
}
// Method returns http method in request.
@@ -782,10 +784,10 @@ func (f *FormData) Reset() {
// File is a struct which support send files via request.
type File struct {
+ reader io.ReadCloser
name string
fieldName string
path string
- reader io.ReadCloser
}
// SetName method sets file name.
diff --git a/client/request_test.go b/client/request_test.go
index e5369fbb..0593d048 100644
--- a/client/request_test.go
+++ b/client/request_test.go
@@ -84,7 +84,7 @@ func Test_Request_Context(t *testing.T) {
require.Nil(t, ctx.Value(key))
- ctx = context.WithValue(ctx, key, "string")
+ ctx = context.WithValue(ctx, key, "string") //nolint: staticcheck // not needed for tests
req.SetContext(ctx)
ctx = req.Context()
@@ -222,12 +222,12 @@ func Test_Request_QueryParam(t *testing.T) {
t.Parallel()
type args struct {
- TInt int
TString string
- TFloat float64
- TBool bool
TSlice []string
TIntSlice []int `param:"int_slice"`
+ TInt int
+ TFloat float64
+ TBool bool
}
p := AcquireRequest()
@@ -334,8 +334,8 @@ func Test_Request_Cookie(t *testing.T) {
t.Run("set cookies with struct", func(t *testing.T) {
t.Parallel()
type args struct {
- CookieInt int `cookie:"int"`
CookieString string `cookie:"string"`
+ CookieInt int `cookie:"int"`
}
req := AcquireRequest().SetCookiesWithStruct(&args{
@@ -396,8 +396,8 @@ func Test_Request_PathParam(t *testing.T) {
t.Run("set path params with struct", func(t *testing.T) {
t.Parallel()
type args struct {
- CookieInt int `path:"int"`
CookieString string `path:"string"`
+ CookieInt int `path:"int"`
}
req := AcquireRequest().SetPathParamsWithStruct(&args{
@@ -510,12 +510,12 @@ func Test_Request_FormData(t *testing.T) {
t.Parallel()
type args struct {
- TInt int
TString string
- TFloat float64
- TBool bool
TSlice []string
TIntSlice []int `form:"int_slice"`
+ TInt int
+ TFloat float64
+ TBool bool
}
p := AcquireRequest()
@@ -1299,13 +1299,13 @@ func Test_SetValWithStruct(t *testing.T) {
// test SetValWithStruct vai QueryParam struct.
type args struct {
- unexport int
- TInt int
TString string
- TFloat float64
- TBool bool
TSlice []string
TIntSlice []int `param:"int_slice"`
+ unexport int
+ TInt int
+ TFloat float64
+ TBool bool
}
t.Run("the struct should be applied", func(t *testing.T) {
@@ -1340,7 +1340,7 @@ func Test_SetValWithStruct(t *testing.T) {
require.True(t, func() bool {
for _, v := range p.PeekMulti("TSlice") {
- if string(v) == "bar" {
+ if string(v) == "bar" { //nolint:goconst // test
return true
}
}
@@ -1453,13 +1453,13 @@ func Test_SetValWithStruct(t *testing.T) {
func Benchmark_SetValWithStruct(b *testing.B) {
// test SetValWithStruct vai QueryParam struct.
type args struct {
- unexport int
- TInt int
TString string
- TFloat float64
- TBool bool
TSlice []string
TIntSlice []int `param:"int_slice"`
+ unexport int
+ TInt int
+ TFloat float64
+ TBool bool
}
b.Run("the struct should be applied", func(b *testing.B) {
@@ -1603,8 +1603,8 @@ func Benchmark_SetValWithStruct(b *testing.B) {
require.Empty(b, string(p.Peek("TInt")))
require.Empty(b, string(p.Peek("TString")))
require.Empty(b, string(p.Peek("TFloat")))
- require.Empty(b, len(p.PeekMulti("TSlice")))
- require.Empty(b, len(p.PeekMulti("int_slice")))
+ require.Empty(b, p.PeekMulti("TSlice"))
+ require.Empty(b, p.PeekMulti("int_slice"))
})
b.Run("error type should ignore", func(b *testing.B) {
diff --git a/client/response.go b/client/response.go
index adb70ac4..a8a032b6 100644
--- a/client/response.go
+++ b/client/response.go
@@ -8,7 +8,6 @@ import (
"io/fs"
"os"
"path/filepath"
- "strings"
"sync"
"github.com/gofiber/utils/v2"
@@ -19,9 +18,9 @@ import (
type Response struct {
client *Client
request *Request
- cookie []*fasthttp.Cookie
RawResponse *fasthttp.Response
+ cookie []*fasthttp.Cookie
}
// setClient method sets client object in response instance.
@@ -68,7 +67,7 @@ func (r *Response) Body() []byte {
// String method returns the body of the server response as String.
func (r *Response) String() string {
- return strings.TrimSpace(string(r.Body()))
+ return utils.Trim(string(r.Body()), ' ')
}
// JSON method will unmarshal body to json.
diff --git a/ctx.go b/ctx.go
index 78eedc19..4d7417ee 100644
--- a/ctx.go
+++ b/ctx.go
@@ -50,24 +50,24 @@ const userContextKey contextKey = 0 // __local_user_context__
type DefaultCtx struct {
app *App // Reference to *App
route *Route // Reference to *Route
- indexRoute int // Index of the current route
- indexHandler int // Index of the current handler
- method string // HTTP method
- methodINT int // HTTP method INT equivalent
- baseURI string // HTTP base uri
- path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
- pathBuffer []byte // HTTP path buffer
- detectionPath string // Route detection path -> string copy from detectionPathBuffer
- detectionPathBuffer []byte // HTTP detectionPath buffer
- treePath string // Path for the search in the tree
- pathOriginal string // Original HTTP path
- values [maxParams]string // Route parameter values
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
- matched bool // Non use route matched
- viewBindMap sync.Map // Default view map to bind template engine
bind *Bind // Default bind reference
redirect *Redirect // Default redirect reference
- redirectionMessages []string // Messages of the previous redirect
+ values [maxParams]string // Route parameter values
+ viewBindMap sync.Map // Default view map to bind template engine
+ method string // HTTP method
+ baseURI string // HTTP base uri
+ path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
+ detectionPath string // Route detection path -> string copy from detectionPathBuffer
+ treePath string // Path for the search in the tree
+ pathOriginal string // Original HTTP path
+ pathBuffer []byte // HTTP path buffer
+ detectionPathBuffer []byte // HTTP detectionPath buffer
+ flashMessages redirectionMsgs // Flash messages
+ indexRoute int // Index of the current route
+ indexHandler int // Index of the current handler
+ methodINT int // HTTP method INT equivalent
+ matched bool // Non use route matched
}
// SendFile defines configuration options when to transfer file with SendFile.
@@ -112,8 +112,8 @@ type SendFile struct {
// sendFileStore is used to keep the SendFile configuration and the handler.
type sendFileStore struct {
handler fasthttp.RequestHandler
- config SendFile
cacheControlValue string
+ config SendFile
}
// compareConfig compares the current SendFile config with the new one
@@ -175,15 +175,15 @@ type RangeSet struct {
// Cookie data for c.Cookie
type Cookie struct {
+ Expires time.Time `json:"expires"` // The expiration date of the cookie
Name string `json:"name"` // The name of the cookie
Value string `json:"value"` // The value of the cookie
Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
+ SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
- Expires time.Time `json:"expires"` // The expiration date of the cookie
Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
- SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
}
@@ -196,8 +196,8 @@ type Views interface {
// ResFmt associates a Content Type to a fiber.Handler for c.Format
type ResFmt struct {
- MediaType string
Handler func(Ctx) error
+ MediaType string
}
// Accepts checks if the specified extensions or content types are acceptable.
@@ -273,10 +273,7 @@ func (c *DefaultCtx) BaseURL() string {
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) BodyRaw() []byte {
- if c.app.config.Immutable {
- return utils.CopyBytes(c.fasthttp.Request.Body())
- }
- return c.fasthttp.Request.Body()
+ return c.getBody()
}
func (c *DefaultCtx) tryDecodeBodyInOrder(
@@ -339,21 +336,19 @@ func (c *DefaultCtx) Body() []byte {
encodingOrder = []string{"", "", ""}
)
- // faster than peek
- c.Request().Header.VisitAll(func(key, value []byte) {
- if c.app.getString(key) == HeaderContentEncoding {
- headerEncoding = c.app.getString(value)
- }
- })
+ // Get Content-Encoding header
+ headerEncoding = utils.UnsafeString(c.Request().Header.ContentEncoding())
+
+ // If no encoding is provided, return the original body
+ if len(headerEncoding) == 0 {
+ return c.getBody()
+ }
// Split and get the encodings list, in order to attend the
// rule defined at: https://www.rfc-editor.org/rfc/rfc9110#section-8.4-5
encodingOrder = getSplicedStrList(headerEncoding, encodingOrder)
if len(encodingOrder) == 0 {
- if c.app.config.Immutable {
- return utils.CopyBytes(c.fasthttp.Request.Body())
- }
- return c.fasthttp.Request.Body()
+ return c.getBody()
}
var decodesRealized uint8
@@ -778,7 +773,7 @@ iploop:
i++
}
- s := strings.TrimRight(headerValue[i:j], " ")
+ s := utils.TrimRight(headerValue[i:j], ' ')
if c.app.config.EnableIPValidation {
// Skip validation if IP is clearly not IPv4/IPv6, otherwise validate without allocations
@@ -828,7 +823,7 @@ func (c *DefaultCtx) extractIPFromHeader(header string) string {
i++
}
- s := strings.TrimRight(headerValue[i:j], " ")
+ s := utils.TrimRight(headerValue[i:j], ' ')
if c.app.config.EnableIPValidation {
if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) {
@@ -862,7 +857,7 @@ func (c *DefaultCtx) Is(extension string) bool {
}
return strings.HasPrefix(
- strings.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), " "),
+ utils.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), ' '),
extensionHeader,
)
}
@@ -939,7 +934,7 @@ func (c *DefaultCtx) Links(link ...string) {
bb.WriteString(`; rel="` + link[i] + `",`)
}
}
- c.setCanonical(HeaderLink, strings.TrimRight(c.app.getString(bb.Bytes()), ","))
+ c.setCanonical(HeaderLink, utils.TrimRight(c.app.getString(bb.Bytes()), ','))
bytebufferpool.Put(bb)
}
@@ -1023,19 +1018,20 @@ func (c *DefaultCtx) ClientHelloInfo() *tls.ClientHelloInfo {
func (c *DefaultCtx) Next() error {
// Increment handler index
c.indexHandler++
- var err error
+
// Did we execute all route handlers?
if c.indexHandler < len(c.route.Handlers) {
// Continue route stack
- err = c.route.Handlers[c.indexHandler](c)
- } else {
- // Continue handler stack
- if c.app.newCtxFunc != nil {
- _, err = c.app.nextCustom(c)
- } else {
- _, err = c.app.next(c)
- }
+ return c.route.Handlers[c.indexHandler](c)
}
+
+ // Continue handler stack
+ if c.app.newCtxFunc != nil {
+ _, err := c.app.nextCustom(c)
+ return err
+ }
+
+ _, err := c.app.next(c)
return err
}
@@ -1284,8 +1280,8 @@ func (c *DefaultCtx) Range(size int) (Range, error) {
Start int
End int
}{
- start,
- end,
+ Start: start,
+ End: end,
})
}
if len(rangeData.Ranges) < 1 {
@@ -1809,7 +1805,7 @@ func (c *DefaultCtx) configDependentPaths() {
}
// If StrictRouting is disabled, we strip all trailing slashes
if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
- c.detectionPathBuffer = bytes.TrimRight(c.detectionPathBuffer, "/")
+ c.detectionPathBuffer = utils.TrimRight(c.detectionPathBuffer, '/')
}
c.detectionPath = c.app.getString(c.detectionPathBuffer)
@@ -1900,7 +1896,7 @@ func (c *DefaultCtx) release() {
c.route = nil
c.fasthttp = nil
c.bind = nil
- c.redirectionMessages = c.redirectionMessages[:0]
+ c.flashMessages = c.flashMessages[:0]
c.viewBindMap = sync.Map{}
if c.redirect != nil {
ReleaseRedirect(c.redirect)
@@ -1908,6 +1904,14 @@ func (c *DefaultCtx) release() {
}
}
+func (c *DefaultCtx) getBody() []byte {
+ if c.app.config.Immutable {
+ return utils.CopyBytes(c.fasthttp.Request.Body())
+ }
+
+ return c.fasthttp.Request.Body()
+}
+
// Methods to use with next stack.
func (c *DefaultCtx) getMethodINT() int {
return c.methodINT
diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go
index bffbe79d..7709f7c9 100644
--- a/ctx_interface_gen.go
+++ b/ctx_interface_gen.go
@@ -330,6 +330,7 @@ type Ctx interface {
Reset(fctx *fasthttp.RequestCtx)
// Release is a method to reset context fields when to use ReleaseCtx()
release()
+ getBody() []byte
// Methods to use with next stack.
getMethodINT() int
getIndexRoute() int
diff --git a/ctx_test.go b/ctx_test.go
index e27aa421..6dcd7410 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -356,6 +356,26 @@ func Test_Ctx_Body(t *testing.T) {
require.Equal(t, []byte("john=doe"), c.Body())
}
+// go test -run Test_Ctx_BodyRaw
+func Test_Ctx_BodyRaw(t *testing.T) {
+ t.Parallel()
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
+
+ c.Request().SetBodyRaw([]byte("john=doe"))
+ require.Equal(t, []byte("john=doe"), c.BodyRaw())
+}
+
+// go test -run Test_Ctx_BodyRaw_Immutable
+func Test_Ctx_BodyRaw_Immutable(t *testing.T) {
+ t.Parallel()
+ app := New(Config{Immutable: true})
+ c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
+
+ c.Request().SetBodyRaw([]byte("john=doe"))
+ require.Equal(t, []byte("john=doe"), c.BodyRaw())
+}
+
// go test -v -run=^$ -bench=Benchmark_Ctx_Body -benchmem -count=4
func Benchmark_Ctx_Body(b *testing.B) {
const input = "john=doe"
@@ -373,6 +393,40 @@ func Benchmark_Ctx_Body(b *testing.B) {
require.Equal(b, []byte(input), c.Body())
}
+// go test -v -run=^$ -bench=Benchmark_Ctx_BodyRaw -benchmem -count=4
+func Benchmark_Ctx_BodyRaw(b *testing.B) {
+ const input = "john=doe"
+
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
+
+ c.Request().SetBodyRaw([]byte(input))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = c.BodyRaw()
+ }
+
+ require.Equal(b, []byte(input), c.BodyRaw())
+}
+
+// go test -v -run=^$ -bench=Benchmark_Ctx_BodyRaw_Immutable -benchmem -count=4
+func Benchmark_Ctx_BodyRaw_Immutable(b *testing.B) {
+ const input = "john=doe"
+
+ app := New(Config{Immutable: true})
+ c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
+
+ c.Request().SetBodyRaw([]byte(input))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = c.BodyRaw()
+ }
+
+ require.Equal(b, []byte(input), c.BodyRaw())
+}
+
// go test -run Test_Ctx_Body_Immutable
func Test_Ctx_Body_Immutable(t *testing.T) {
t.Parallel()
@@ -509,8 +563,8 @@ func Benchmark_Ctx_Body_With_Compression(b *testing.B) {
}
)
compressionTests := []struct {
- contentEncoding string
compressWriter func([]byte) ([]byte, error)
+ contentEncoding string
}{
{
contentEncoding: "gzip",
@@ -702,8 +756,8 @@ func Benchmark_Ctx_Body_With_Compression_Immutable(b *testing.B) {
}
)
compressionTests := []struct {
- contentEncoding string
compressWriter func([]byte) ([]byte, error)
+ contentEncoding string
}{
{
contentEncoding: "gzip",
@@ -813,7 +867,7 @@ func Test_Ctx_UserContext(t *testing.T) {
t.Parallel()
testKey := struct{}{}
testValue := "Test Value"
- ctx := context.WithValue(context.Background(), testKey, testValue)
+ ctx := context.WithValue(context.Background(), testKey, testValue) //nolint: staticcheck // not needed for tests
require.Equal(t, testValue, ctx.Value(testKey))
})
}
@@ -826,7 +880,7 @@ func Test_Ctx_SetUserContext(t *testing.T) {
testKey := struct{}{}
testValue := "Test Value"
- ctx := context.WithValue(context.Background(), testKey, testValue)
+ ctx := context.WithValue(context.Background(), testKey, testValue) //nolint: staticcheck // not needed for tests
c.SetUserContext(ctx)
require.Equal(t, testValue, c.UserContext().Value(testKey))
}
@@ -846,7 +900,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))
+ ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input)) //nolint: staticcheck // not needed for tests
c.SetUserContext(ctx)
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
@@ -854,7 +908,6 @@ func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) {
// Consecutive Requests
for i := 1; i <= 10; i++ {
- i := i
t.Run(fmt.Sprintf("request_%d", i), func(t *testing.T) {
t.Parallel()
resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil))
@@ -966,7 +1019,7 @@ func Test_Ctx_Format(t *testing.T) {
fmts := []ResFmt{}
for _, t := range types {
t := utils.CopyString(t)
- fmts = append(fmts, ResFmt{t, func(_ Ctx) error {
+ fmts = append(fmts, ResFmt{MediaType: t, Handler: func(_ Ctx) error {
accepted = t
return nil
}})
@@ -988,11 +1041,11 @@ func Test_Ctx_Format(t *testing.T) {
require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
myError := errors.New("this is an error")
- err = c.Format(ResFmt{"text/html", func(_ Ctx) error { return myError }})
+ err = c.Format(ResFmt{MediaType: "text/html", Handler: func(_ Ctx) error { return myError }})
require.ErrorIs(t, err, myError)
c.Request().Header.Set(HeaderAccept, "application/json")
- err = c.Format(ResFmt{"text/html", func(c Ctx) error { return c.SendStatus(StatusOK) }})
+ err = c.Format(ResFmt{MediaType: "text/html", Handler: func(c Ctx) error { return c.SendStatus(StatusOK) }})
require.Equal(t, StatusNotAcceptable, c.Response().StatusCode())
require.NoError(t, err)
@@ -1022,10 +1075,10 @@ func Benchmark_Ctx_Format(b *testing.B) {
b.Run("with arg allocation", func(b *testing.B) {
for n := 0; n < b.N; n++ {
err = c.Format(
- ResFmt{"application/xml", fail},
- ResFmt{"text/html", fail},
- ResFmt{"text/plain;format=fixed", fail},
- ResFmt{"text/plain;format=flowed", ok},
+ ResFmt{MediaType: "application/xml", Handler: fail},
+ ResFmt{MediaType: "text/html", Handler: fail},
+ ResFmt{MediaType: "text/plain;format=fixed", Handler: fail},
+ ResFmt{MediaType: "text/plain;format=flowed", Handler: ok},
)
}
require.NoError(b, err)
@@ -1033,10 +1086,10 @@ func Benchmark_Ctx_Format(b *testing.B) {
b.Run("pre-allocated args", func(b *testing.B) {
offers := []ResFmt{
- {"application/xml", fail},
- {"text/html", fail},
- {"text/plain;format=fixed", fail},
- {"text/plain;format=flowed", ok},
+ {MediaType: "application/xml", Handler: fail},
+ {MediaType: "text/html", Handler: fail},
+ {MediaType: "text/plain;format=fixed", Handler: fail},
+ {MediaType: "text/plain;format=flowed", Handler: ok},
}
for n := 0; n < b.N; n++ {
err = c.Format(offers...)
@@ -1047,8 +1100,8 @@ func Benchmark_Ctx_Format(b *testing.B) {
c.Request().Header.Set("Accept", "text/plain")
b.Run("text/plain", func(b *testing.B) {
offers := []ResFmt{
- {"application/xml", fail},
- {"text/plain", ok},
+ {MediaType: "application/xml", Handler: fail},
+ {MediaType: "text/plain", Handler: ok},
}
for n := 0; n < b.N; n++ {
err = c.Format(offers...)
@@ -1059,9 +1112,9 @@ func Benchmark_Ctx_Format(b *testing.B) {
c.Request().Header.Set("Accept", "json")
b.Run("json", func(b *testing.B) {
offers := []ResFmt{
- {"xml", fail},
- {"html", fail},
- {"json", ok},
+ {MediaType: "xml", Handler: fail},
+ {MediaType: "html", Handler: fail},
+ {MediaType: "json", Handler: ok},
}
for n := 0; n < b.N; n++ {
err = c.Format(offers...)
@@ -1123,9 +1176,9 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Message struct {
- Recipients []string
Sender string `xml:"sender,attr"`
- Urgency int `xml:"urgency,attr"`
+ Recipients []string
+ Urgency int `xml:"urgency,attr"`
}
data := Message{
Recipients: []string{"Alice", "Bob"},
@@ -1137,7 +1190,7 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
err := c.AutoFormat(data)
require.NoError(t, err)
require.Equal(t,
- `{"Recipients":["Alice","Bob"],"Sender":"Carol","Urgency":3}`,
+ `{"Sender":"Carol","Recipients":["Alice","Bob"],"Urgency":3}`,
string(c.Response().Body()),
)
@@ -1370,11 +1423,11 @@ func Test_Ctx_Binders(t *testing.T) {
}
type TestStruct struct {
+ Name string
+ NameWithDefault string `json:"name2" xml:"Name2" form:"name2" cookie:"name2" query:"name2" params:"name2" header:"Name2"`
TestEmbeddedStruct
- Name string
Class int
- NameWithDefault string `json:"name2" xml:"Name2" form:"name2" cookie:"name2" query:"name2" params:"name2" header:"Name2"`
- ClassWithDefault int `json:"class2" xml:"Class2" form:"class2" cookie:"class2" query:"class2" params:"class2" header:"Class2"`
+ ClassWithDefault int `json:"class2" xml:"Class2" form:"class2" cookie:"class2" query:"class2" params:"class2" header:"Class2"`
}
withValues := func(t *testing.T, actionFn func(c Ctx, testStruct *TestStruct) error) {
@@ -2141,11 +2194,11 @@ func Test_Ctx_Locals_GenericCustomStruct(t *testing.T) {
app := New()
app.Use(func(c Ctx) error {
- Locals[User](c, "user", User{"john", 18})
+ Locals[User](c, "user", User{name: "john", age: 18})
return c.Next()
})
app.Use("/test", func(c Ctx) error {
- require.Equal(t, User{"john", 18}, Locals[User](c, "user"))
+ require.Equal(t, User{name: "john", age: 18}, Locals[User](c, "user"))
return nil
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
@@ -2697,13 +2750,13 @@ func Test_Ctx_Range(t *testing.T) {
testRange("bytes=")
testRange("bytes=500=")
testRange("bytes=500-300")
- testRange("bytes=a-700", RangeSet{300, 999})
- testRange("bytes=500-b", RangeSet{500, 999})
- testRange("bytes=500-1000", RangeSet{500, 999})
- testRange("bytes=500-700", RangeSet{500, 700})
- testRange("bytes=0-0,2-1000", RangeSet{0, 0}, RangeSet{2, 999})
- testRange("bytes=0-99,450-549,-100", RangeSet{0, 99}, RangeSet{450, 549}, RangeSet{900, 999})
- testRange("bytes=500-700,601-999", RangeSet{500, 700}, RangeSet{601, 999})
+ testRange("bytes=a-700", RangeSet{Start: 300, End: 999})
+ testRange("bytes=500-b", RangeSet{Start: 500, End: 999})
+ testRange("bytes=500-1000", RangeSet{Start: 500, End: 999})
+ testRange("bytes=500-700", RangeSet{Start: 500, End: 700})
+ testRange("bytes=0-0,2-1000", RangeSet{Start: 0, End: 0}, RangeSet{Start: 2, End: 999})
+ testRange("bytes=0-99,450-549,-100", RangeSet{Start: 0, End: 99}, RangeSet{Start: 450, End: 549}, RangeSet{Start: 900, End: 999})
+ testRange("bytes=500-700,601-999", RangeSet{Start: 500, End: 700}, RangeSet{Start: 601, End: 999})
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4
@@ -2717,10 +2770,10 @@ func Benchmark_Ctx_Range(b *testing.B) {
start int
end int
}{
- {"bytes=-700", 300, 999},
- {"bytes=500-", 500, 999},
- {"bytes=500-1000", 500, 999},
- {"bytes=0-700,800-1000", 0, 700},
+ {str: "bytes=-700", start: 300, end: 999},
+ {str: "bytes=500-", start: 500, end: 999},
+ {str: "bytes=500-1000", start: 500, end: 999},
+ {str: "bytes=0-700,800-1000", start: 0, end: 700},
}
for _, tc := range testCases {
@@ -3068,8 +3121,6 @@ func Test_Static_Compress(t *testing.T) {
// Note: deflate is not supported by fasthttp.FS
algorithms := []string{"zstd", "gzip", "br"}
for _, algo := range algorithms {
- algo := algo
-
t.Run(algo+"_compression", func(t *testing.T) {
t.Parallel()
@@ -3099,23 +3150,25 @@ func Test_Ctx_SendFile_Compress_CheckCompressed(t *testing.T) {
expectedFileContent, err := io.ReadAll(f)
require.NoError(t, err)
- sendFileBodyReader := func(compression string) []byte {
- reqCtx := &fasthttp.RequestCtx{}
- reqCtx.Request.Header.Add(HeaderAcceptEncoding, compression)
+ sendFileBodyReader := func(compression string) ([]byte, error) {
+ t.Helper()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+ c.Request().Header.Add(HeaderAcceptEncoding, compression)
- c := app.AcquireCtx(reqCtx)
- err = c.SendFile("./ctx.go", SendFile{
+ err := c.SendFile("./ctx.go", SendFile{
Compress: true,
})
- require.NoError(t, err)
- return c.Response().Body()
+ return c.Response().Body(), err
}
t.Run("gzip", func(t *testing.T) {
t.Parallel()
- body, err := fasthttp.AppendGunzipBytes(nil, sendFileBodyReader("gzip"))
+ b, err := sendFileBodyReader("gzip")
+ require.NoError(t, err)
+ body, err := fasthttp.AppendGunzipBytes(nil, b)
require.NoError(t, err)
require.Equal(t, expectedFileContent, body)
@@ -3124,7 +3177,9 @@ func Test_Ctx_SendFile_Compress_CheckCompressed(t *testing.T) {
t.Run("zstd", func(t *testing.T) {
t.Parallel()
- body, err := fasthttp.AppendUnzstdBytes(nil, sendFileBodyReader("zstd"))
+ b, err := sendFileBodyReader("zstd")
+ require.NoError(t, err)
+ body, err := fasthttp.AppendUnzstdBytes(nil, b)
require.NoError(t, err)
require.Equal(t, expectedFileContent, body)
@@ -3133,7 +3188,9 @@ func Test_Ctx_SendFile_Compress_CheckCompressed(t *testing.T) {
t.Run("br", func(t *testing.T) {
t.Parallel()
- body, err := fasthttp.AppendUnbrotliBytes(nil, sendFileBodyReader("br"))
+ b, err := sendFileBodyReader("br")
+ require.NoError(t, err)
+ body, err := fasthttp.AppendUnbrotliBytes(nil, b)
require.NoError(t, err)
require.Equal(t, expectedFileContent, body)
@@ -3223,12 +3280,12 @@ func Test_Ctx_SendFile_Multiple(t *testing.T) {
body string
contentDisposition string
}{
- {"/test?file=1", "type DefaultCtx struct", ""},
- {"/test?file=2", "type App struct", ""},
- {"/test?file=3", "type DefaultCtx struct", "attachment"},
- {"/test?file=4", "Test_App_MethodNotAllowed", ""},
- {"/test2", "type DefaultCtx struct", "attachment"},
- {"/test2", "type DefaultCtx struct", "attachment"},
+ {url: "/test?file=1", body: "type DefaultCtx struct", contentDisposition: ""},
+ {url: "/test?file=2", body: "type App struct", contentDisposition: ""},
+ {url: "/test?file=3", body: "type DefaultCtx struct", contentDisposition: "attachment"},
+ {url: "/test?file=4", body: "Test_App_MethodNotAllowed", contentDisposition: ""},
+ {url: "/test2", body: "type DefaultCtx struct", contentDisposition: "attachment"},
+ {url: "/test2", body: "type DefaultCtx struct", contentDisposition: "attachment"},
}
for _, tc := range testCases {
@@ -3242,6 +3299,8 @@ func Test_Ctx_SendFile_Multiple(t *testing.T) {
require.Contains(t, string(body), tc.body)
}
+ app.sendfilesMutex.RLock()
+ defer app.sendfilesMutex.RUnlock()
require.Len(t, app.sendfiles, 3)
}
@@ -3276,7 +3335,6 @@ func Test_Ctx_SendFile_Immutable(t *testing.T) {
}
for _, endpoint := range endpointsForTest {
- endpoint := endpoint
t.Run(endpoint, func(t *testing.T) {
t.Parallel()
// 1st try
diff --git a/docs/api/app.md b/docs/api/app.md
index 164b1edc..ef9c2ea0 100644
--- a/docs/api/app.md
+++ b/docs/api/app.md
@@ -43,19 +43,19 @@ func (app *App) MountPath() string
```go title="Examples"
func main() {
- app := fiber.New()
- one := fiber.New()
- two := fiber.New()
- three := fiber.New()
+ app := fiber.New()
+ one := fiber.New()
+ two := fiber.New()
+ three := fiber.New()
- two.Use("/three", three)
- one.Use("/two", two)
- app.Use("/one", one)
+ 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() // ""
+ one.MountPath() // "/one"
+ two.MountPath() // "/one/two"
+ three.MountPath() // "/one/two/three"
+ app.MountPath() // ""
}
```
@@ -91,7 +91,7 @@ func main() {
### Route
-Returns an instance of a single route, which you can then use to handle HTTP verbs with optional middleware.
+Returns an instance of a single route, which you can then use to handle HTTP verbs with optional middleware.
Similar to [`Express`](https://expressjs.com/de/api.html#app.route).
@@ -120,6 +120,7 @@ type Register interface {
Route(path string) Register
}
```
+
```go title="Examples"
@@ -222,6 +223,7 @@ func main() {
]
]
```
+
### Name
@@ -328,6 +330,7 @@ func main() {
null
]
```
+
### GetRoute
@@ -347,10 +350,10 @@ func main() {
app.Get("/", handler).Name("index")
data, _ := json.MarshalIndent(app.GetRoute("index"), "", " ")
- fmt.Print(string(data))
+ fmt.Print(string(data))
- app.Listen(":3000")
+ app.Listen(":3000")
}
```
@@ -365,6 +368,7 @@ func main() {
"params": null
}
```
+
### GetRoutes
@@ -376,14 +380,15 @@ func (app *App) GetRoutes(filterUseOption ...bool) []Route
```
When filterUseOption equal to true, it will filter the routes registered by the middleware.
+
```go title="Examples"
func main() {
- app := fiber.New()
- app.Post("/", func (c fiber.Ctx) error {
- return c.SendString("Hello, World!")
- }).Name("index")
- data, _ := json.MarshalIndent(app.GetRoutes(true), "", " ")
- fmt.Print(string(data))
+ app := fiber.New()
+ app.Post("/", func (c fiber.Ctx) error {
+ return c.SendString("Hello, World!")
+ }).Name("index")
+ data, _ := json.MarshalIndent(app.GetRoutes(true), "", " ")
+ fmt.Print(string(data))
}
```
@@ -400,6 +405,7 @@ func main() {
}
]
```
+
## Config
@@ -436,12 +442,12 @@ func (app *App) NewCtxFunc(function func(app *App) CustomCtx)
```go title="Examples"
type CustomCtx struct {
- DefaultCtx
+ DefaultCtx
}
// Custom method
func (c *CustomCtx) Params(key string, defaultValue ...string) string {
- return "prefix_" + c.DefaultCtx.Params(key)
+ return "prefix_" + c.DefaultCtx.Params(key)
}
app := New()
@@ -521,7 +527,6 @@ func (app *App) RegisterCustomConstraint(constraint CustomConstraint)
See [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.
@@ -568,3 +573,31 @@ Hooks is a method to return [hooks](./hooks.md) property.
```go title="Signature"
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.
+
+```go title="Signature"
+func (app *App) RebuildTree() *App
+```
+
+**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in development mode. Avoid using it concurrently.
+
+### Example Usage
+
+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)
+ })
+
+ app.RebuildTree() // Rebuild the route tree to register the new route
+
+ return c.SendStatus(http.StatusOK)
+})
+```
+
+In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available.
diff --git a/docs/api/bind.md b/docs/api/bind.md
index f955202e..927d423b 100644
--- a/docs/api/bind.md
+++ b/docs/api/bind.md
@@ -15,14 +15,13 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more..
:::
-
## Binders
- [Body](#body)
- - [Form](#form)
- - [JSON](#json)
- - [MultipartForm](#multipartform)
- - [XML](#xml)
+ - [Form](#form)
+ - [JSON](#json)
+ - [MultipartForm](#multipartform)
+ - [XML](#xml)
- [Cookie](#cookie)
- [Header](#header)
- [Query](#query)
@@ -80,7 +79,6 @@ app.Post("/", func(c fiber.Ctx) error {
// curl -X POST "http://localhost:3000/?name=john&pass=doe"
```
-
**The methods for the various bodies can also be used directly:**
#### Form
@@ -89,7 +87,6 @@ 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"`.
-
```go title="Signature"
func (b *Bind) Form(out any) error
```
@@ -125,7 +122,6 @@ 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"`.
-
```go title="Signature"
func (b *Bind) JSON(out any) error
```
@@ -162,7 +158,6 @@ 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"`.
-
```go title="Signature"
func (b *Bind) MultipartForm(out any) error
```
@@ -197,8 +192,7 @@ app.Post("/", func(c fiber.Ctx) error {
Binds the request xml 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 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 of `xml:"pass"`.
```go title="Signature"
func (b *Bind) XML(out any) error
@@ -229,7 +223,6 @@ app.Post("/", func(c fiber.Ctx) error {
// 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.
@@ -262,7 +255,6 @@ app.Get("/", func(c fiber.Ctx) error {
// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/
```
-
### Header
This method is similar to [Body-Binding](#body), but for request headers.
@@ -298,7 +290,6 @@ app.Get("/", func(c fiber.Ctx) error {
// 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.
@@ -342,7 +333,6 @@ app.Get("/", func(c fiber.Ctx) error {
For more parser settings please look here [Config](fiber.md#enablesplittingonparsers)
:::
-
### RespHeader
This method is similar to [Body-Binding](#body), but for response headers.
@@ -467,7 +457,6 @@ It's default behavior of binder.
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.
@@ -542,7 +531,6 @@ app.Get("/query", func(c fiber.Ctx) error {
```
-
## Validation
Validation is also possible with the binding methods. You can specify your validation rules using the `validate` struct tag.
@@ -585,6 +573,3 @@ app.Post("/", func(c fiber.Ctx) error {
}
})
```
-
-
-
diff --git a/docs/api/constants.md b/docs/api/constants.md
index a9ee6d5a..9fbff533 100644
--- a/docs/api/constants.md
+++ b/docs/api/constants.md
@@ -5,7 +5,7 @@ description: Some constants for Fiber.
sidebar_position: 8
---
-### HTTP methods were copied from net/http.
+### HTTP methods were copied from net/http
```go
const (
@@ -26,94 +26,94 @@ const (
```go
const (
- MIMETextXML = "text/xml"
- MIMETextHTML = "text/html"
- MIMETextPlain = "text/plain"
- MIMETextJavaScript = "text/javascript"
- MIMETextCSS = "text/css"
- MIMEApplicationXML = "application/xml"
+ MIMETextXML = "text/xml"
+ MIMETextHTML = "text/html"
+ MIMETextPlain = "text/plain"
+ MIMETextJavaScript = "text/javascript"
+ MIMETextCSS = "text/css"
+ MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json"
- MIMEApplicationJavaScript = "application/javascript"
- MIMEApplicationForm = "application/x-www-form-urlencoded"
- MIMEOctetStream = "application/octet-stream"
- MIMEMultipartForm = "multipart/form-data"
+ MIMEApplicationJavaScript = "application/javascript"
+ MIMEApplicationForm = "application/x-www-form-urlencoded"
+ MIMEOctetStream = "application/octet-stream"
+ MIMEMultipartForm = "multipart/form-data"
- MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
- MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
- MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
- MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
- MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
- MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
- MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
- MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
+ MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
+ MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
+ MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
+ MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
+ MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
+ MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
+ MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
+ MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
)```
### HTTP status codes were copied from net/http.
```go
const (
- StatusContinue = 100 // RFC 7231, 6.2.1
- StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
- StatusProcessing = 102 // RFC 2518, 10.1
- StatusEarlyHints = 103 // RFC 8297
- StatusOK = 200 // RFC 7231, 6.3.1
- StatusCreated = 201 // RFC 7231, 6.3.2
- StatusAccepted = 202 // RFC 7231, 6.3.3
- StatusNonAuthoritativeInformation = 203 // RFC 7231, 6.3.4
- StatusNoContent = 204 // RFC 7231, 6.3.5
- StatusResetContent = 205 // RFC 7231, 6.3.6
- StatusPartialContent = 206 // RFC 7233, 4.1
- StatusMultiStatus = 207 // RFC 4918, 11.1
- StatusAlreadyReported = 208 // RFC 5842, 7.1
- StatusIMUsed = 226 // RFC 3229, 10.4.1
- StatusMultipleChoices = 300 // RFC 7231, 6.4.1
- StatusMovedPermanently = 301 // RFC 7231, 6.4.2
- StatusFound = 302 // RFC 7231, 6.4.3
- StatusSeeOther = 303 // RFC 7231, 6.4.4
- StatusNotModified = 304 // RFC 7232, 4.1
- StatusUseProxy = 305 // RFC 7231, 6.4.5
- StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
- StatusPermanentRedirect = 308 // RFC 7538, 3
- StatusBadRequest = 400 // RFC 7231, 6.5.1
- StatusUnauthorized = 401 // RFC 7235, 3.1
- StatusPaymentRequired = 402 // RFC 7231, 6.5.2
- StatusForbidden = 403 // RFC 7231, 6.5.3
- StatusNotFound = 404 // RFC 7231, 6.5.4
- StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
- StatusNotAcceptable = 406 // RFC 7231, 6.5.6
- StatusProxyAuthRequired = 407 // RFC 7235, 3.2
- StatusRequestTimeout = 408 // RFC 7231, 6.5.7
- StatusConflict = 409 // RFC 7231, 6.5.8
- StatusGone = 410 // RFC 7231, 6.5.9
- StatusLengthRequired = 411 // RFC 7231, 6.5.10
- StatusPreconditionFailed = 412 // RFC 7232, 4.2
- StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
- StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
- StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
- StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
- StatusExpectationFailed = 417 // RFC 7231, 6.5.14
- StatusTeapot = 418 // RFC 7168, 2.3.3
- StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2
- StatusUnprocessableEntity = 422 // RFC 4918, 11.2
- StatusLocked = 423 // RFC 4918, 11.3
- StatusFailedDependency = 424 // RFC 4918, 11.4
- StatusTooEarly = 425 // RFC 8470, 5.2.
- StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
- StatusPreconditionRequired = 428 // RFC 6585, 3
- StatusTooManyRequests = 429 // RFC 6585, 4
- StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
- StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
- StatusInternalServerError = 500 // RFC 7231, 6.6.1
- StatusNotImplemented = 501 // RFC 7231, 6.6.2
- StatusBadGateway = 502 // RFC 7231, 6.6.3
- StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
- StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
- StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
- StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
- StatusInsufficientStorage = 507 // RFC 4918, 11.5
- StatusLoopDetected = 508 // RFC 5842, 7.2
- StatusNotExtended = 510 // RFC 2774, 7
- StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
+ StatusContinue = 100 // RFC 7231, 6.2.1
+ StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
+ StatusProcessing = 102 // RFC 2518, 10.1
+ StatusEarlyHints = 103 // RFC 8297
+ StatusOK = 200 // RFC 7231, 6.3.1
+ StatusCreated = 201 // RFC 7231, 6.3.2
+ StatusAccepted = 202 // RFC 7231, 6.3.3
+ StatusNonAuthoritativeInformation = 203 // RFC 7231, 6.3.4
+ StatusNoContent = 204 // RFC 7231, 6.3.5
+ StatusResetContent = 205 // RFC 7231, 6.3.6
+ StatusPartialContent = 206 // RFC 7233, 4.1
+ StatusMultiStatus = 207 // RFC 4918, 11.1
+ StatusAlreadyReported = 208 // RFC 5842, 7.1
+ StatusIMUsed = 226 // RFC 3229, 10.4.1
+ StatusMultipleChoices = 300 // RFC 7231, 6.4.1
+ StatusMovedPermanently = 301 // RFC 7231, 6.4.2
+ StatusFound = 302 // RFC 7231, 6.4.3
+ StatusSeeOther = 303 // RFC 7231, 6.4.4
+ StatusNotModified = 304 // RFC 7232, 4.1
+ StatusUseProxy = 305 // RFC 7231, 6.4.5
+ StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
+ StatusPermanentRedirect = 308 // RFC 7538, 3
+ StatusBadRequest = 400 // RFC 7231, 6.5.1
+ StatusUnauthorized = 401 // RFC 7235, 3.1
+ StatusPaymentRequired = 402 // RFC 7231, 6.5.2
+ StatusForbidden = 403 // RFC 7231, 6.5.3
+ StatusNotFound = 404 // RFC 7231, 6.5.4
+ StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
+ StatusNotAcceptable = 406 // RFC 7231, 6.5.6
+ StatusProxyAuthRequired = 407 // RFC 7235, 3.2
+ StatusRequestTimeout = 408 // RFC 7231, 6.5.7
+ StatusConflict = 409 // RFC 7231, 6.5.8
+ StatusGone = 410 // RFC 7231, 6.5.9
+ StatusLengthRequired = 411 // RFC 7231, 6.5.10
+ StatusPreconditionFailed = 412 // RFC 7232, 4.2
+ StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
+ StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
+ StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
+ StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
+ StatusExpectationFailed = 417 // RFC 7231, 6.5.14
+ StatusTeapot = 418 // RFC 7168, 2.3.3
+ StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2
+ StatusUnprocessableEntity = 422 // RFC 4918, 11.2
+ StatusLocked = 423 // RFC 4918, 11.3
+ StatusFailedDependency = 424 // RFC 4918, 11.4
+ StatusTooEarly = 425 // RFC 8470, 5.2.
+ StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
+ StatusPreconditionRequired = 428 // RFC 6585, 3
+ StatusTooManyRequests = 429 // RFC 6585, 4
+ StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
+ StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
+ StatusInternalServerError = 500 // RFC 7231, 6.6.1
+ StatusNotImplemented = 501 // RFC 7231, 6.6.2
+ StatusBadGateway = 502 // RFC 7231, 6.6.3
+ StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
+ StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
+ StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
+ StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
+ StatusInsufficientStorage = 507 // RFC 4918, 11.5
+ StatusLoopDetected = 508 // RFC 5842, 7.2
+ StatusNotExtended = 510 // RFC 2774, 7
+ StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
```
@@ -121,46 +121,46 @@ const (
```go
var (
- ErrBadRequest = NewError(StatusBadRequest) // RFC 7231, 6.5.1
- ErrUnauthorized = NewError(StatusUnauthorized) // RFC 7235, 3.1
- ErrPaymentRequired = NewError(StatusPaymentRequired) // RFC 7231, 6.5.2
- ErrForbidden = NewError(StatusForbidden) // RFC 7231, 6.5.3
- ErrNotFound = NewError(StatusNotFound) // RFC 7231, 6.5.4
- ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // RFC 7231, 6.5.5
- ErrNotAcceptable = NewError(StatusNotAcceptable) // RFC 7231, 6.5.6
- ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // RFC 7235, 3.2
- ErrRequestTimeout = NewError(StatusRequestTimeout) // RFC 7231, 6.5.7
- ErrConflict = NewError(StatusConflict) // RFC 7231, 6.5.8
- ErrGone = NewError(StatusGone) // RFC 7231, 6.5.9
- ErrLengthRequired = NewError(StatusLengthRequired) // RFC 7231, 6.5.10
- ErrPreconditionFailed = NewError(StatusPreconditionFailed) // RFC 7232, 4.2
- ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // RFC 7231, 6.5.11
- ErrRequestURITooLong = NewError(StatusRequestURITooLong) // RFC 7231, 6.5.12
- ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // RFC 7231, 6.5.13
- ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // RFC 7233, 4.4
- ErrExpectationFailed = NewError(StatusExpectationFailed) // RFC 7231, 6.5.14
- ErrTeapot = NewError(StatusTeapot) // RFC 7168, 2.3.3
- ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // RFC 7540, 9.1.2
- ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // RFC 4918, 11.2
- ErrLocked = NewError(StatusLocked) // RFC 4918, 11.3
- ErrFailedDependency = NewError(StatusFailedDependency) // RFC 4918, 11.4
- ErrTooEarly = NewError(StatusTooEarly) // RFC 8470, 5.2.
- ErrUpgradeRequired = NewError(StatusUpgradeRequired) // RFC 7231, 6.5.15
- ErrPreconditionRequired = NewError(StatusPreconditionRequired) // RFC 6585, 3
- ErrTooManyRequests = NewError(StatusTooManyRequests) // RFC 6585, 4
- ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // RFC 6585, 5
- ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // RFC 7725, 3
- ErrInternalServerError = NewError(StatusInternalServerError) // RFC 7231, 6.6.1
- ErrNotImplemented = NewError(StatusNotImplemented) // RFC 7231, 6.6.2
- ErrBadGateway = NewError(StatusBadGateway) // RFC 7231, 6.6.3
- ErrServiceUnavailable = NewError(StatusServiceUnavailable) // RFC 7231, 6.6.4
- ErrGatewayTimeout = NewError(StatusGatewayTimeout) // RFC 7231, 6.6.5
- ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // RFC 7231, 6.6.6
- ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // RFC 2295, 8.1
- ErrInsufficientStorage = NewError(StatusInsufficientStorage) // RFC 4918, 11.5
- ErrLoopDetected = NewError(StatusLoopDetected) // RFC 5842, 7.2
- ErrNotExtended = NewError(StatusNotExtended) // RFC 2774, 7
- ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // RFC 6585, 6
+ ErrBadRequest = NewError(StatusBadRequest) // RFC 7231, 6.5.1
+ ErrUnauthorized = NewError(StatusUnauthorized) // RFC 7235, 3.1
+ ErrPaymentRequired = NewError(StatusPaymentRequired) // RFC 7231, 6.5.2
+ ErrForbidden = NewError(StatusForbidden) // RFC 7231, 6.5.3
+ ErrNotFound = NewError(StatusNotFound) // RFC 7231, 6.5.4
+ ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // RFC 7231, 6.5.5
+ ErrNotAcceptable = NewError(StatusNotAcceptable) // RFC 7231, 6.5.6
+ ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // RFC 7235, 3.2
+ ErrRequestTimeout = NewError(StatusRequestTimeout) // RFC 7231, 6.5.7
+ ErrConflict = NewError(StatusConflict) // RFC 7231, 6.5.8
+ ErrGone = NewError(StatusGone) // RFC 7231, 6.5.9
+ ErrLengthRequired = NewError(StatusLengthRequired) // RFC 7231, 6.5.10
+ ErrPreconditionFailed = NewError(StatusPreconditionFailed) // RFC 7232, 4.2
+ ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // RFC 7231, 6.5.11
+ ErrRequestURITooLong = NewError(StatusRequestURITooLong) // RFC 7231, 6.5.12
+ ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // RFC 7231, 6.5.13
+ ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // RFC 7233, 4.4
+ ErrExpectationFailed = NewError(StatusExpectationFailed) // RFC 7231, 6.5.14
+ ErrTeapot = NewError(StatusTeapot) // RFC 7168, 2.3.3
+ ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // RFC 7540, 9.1.2
+ ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // RFC 4918, 11.2
+ ErrLocked = NewError(StatusLocked) // RFC 4918, 11.3
+ ErrFailedDependency = NewError(StatusFailedDependency) // RFC 4918, 11.4
+ ErrTooEarly = NewError(StatusTooEarly) // RFC 8470, 5.2.
+ ErrUpgradeRequired = NewError(StatusUpgradeRequired) // RFC 7231, 6.5.15
+ ErrPreconditionRequired = NewError(StatusPreconditionRequired) // RFC 6585, 3
+ ErrTooManyRequests = NewError(StatusTooManyRequests) // RFC 6585, 4
+ ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // RFC 6585, 5
+ ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // RFC 7725, 3
+ ErrInternalServerError = NewError(StatusInternalServerError) // RFC 7231, 6.6.1
+ ErrNotImplemented = NewError(StatusNotImplemented) // RFC 7231, 6.6.2
+ ErrBadGateway = NewError(StatusBadGateway) // RFC 7231, 6.6.3
+ ErrServiceUnavailable = NewError(StatusServiceUnavailable) // RFC 7231, 6.6.4
+ ErrGatewayTimeout = NewError(StatusGatewayTimeout) // RFC 7231, 6.6.5
+ ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // RFC 7231, 6.6.6
+ ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // RFC 2295, 8.1
+ ErrInsufficientStorage = NewError(StatusInsufficientStorage) // RFC 4918, 11.5
+ ErrLoopDetected = NewError(StatusLoopDetected) // RFC 5842, 7.2
+ ErrNotExtended = NewError(StatusNotExtended) // RFC 2774, 7
+ ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // RFC 6585, 6
)
```
@@ -168,127 +168,127 @@ HTTP Headers were copied from net/http.
```go
const (
- HeaderAuthorization = "Authorization"
- HeaderProxyAuthenticate = "Proxy-Authenticate"
- HeaderProxyAuthorization = "Proxy-Authorization"
- HeaderWWWAuthenticate = "WWW-Authenticate"
- HeaderAge = "Age"
- HeaderCacheControl = "Cache-Control"
- HeaderClearSiteData = "Clear-Site-Data"
- HeaderExpires = "Expires"
- HeaderPragma = "Pragma"
- HeaderWarning = "Warning"
- HeaderAcceptCH = "Accept-CH"
- HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
- HeaderContentDPR = "Content-DPR"
- HeaderDPR = "DPR"
- HeaderEarlyData = "Early-Data"
- HeaderSaveData = "Save-Data"
- HeaderViewportWidth = "Viewport-Width"
- HeaderWidth = "Width"
- HeaderETag = "ETag"
- HeaderIfMatch = "If-Match"
- HeaderIfModifiedSince = "If-Modified-Since"
- HeaderIfNoneMatch = "If-None-Match"
- HeaderIfUnmodifiedSince = "If-Unmodified-Since"
- HeaderLastModified = "Last-Modified"
- HeaderVary = "Vary"
- HeaderConnection = "Connection"
- HeaderKeepAlive = "Keep-Alive"
- HeaderAccept = "Accept"
- HeaderAcceptCharset = "Accept-Charset"
- HeaderAcceptEncoding = "Accept-Encoding"
- HeaderAcceptLanguage = "Accept-Language"
- HeaderCookie = "Cookie"
- HeaderExpect = "Expect"
- HeaderMaxForwards = "Max-Forwards"
- HeaderSetCookie = "Set-Cookie"
- HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
- HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
- HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
- HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
- HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
- HeaderAccessControlMaxAge = "Access-Control-Max-Age"
- HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
- HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
- HeaderOrigin = "Origin"
- HeaderTimingAllowOrigin = "Timing-Allow-Origin"
- HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
- HeaderDNT = "DNT"
- HeaderTk = "Tk"
- HeaderContentDisposition = "Content-Disposition"
- HeaderContentEncoding = "Content-Encoding"
- HeaderContentLanguage = "Content-Language"
- HeaderContentLength = "Content-Length"
- HeaderContentLocation = "Content-Location"
- HeaderContentType = "Content-Type"
- HeaderForwarded = "Forwarded"
- HeaderVia = "Via"
- HeaderXForwardedFor = "X-Forwarded-For"
- HeaderXForwardedHost = "X-Forwarded-Host"
- HeaderXForwardedProto = "X-Forwarded-Proto"
- HeaderXForwardedProtocol = "X-Forwarded-Protocol"
- HeaderXForwardedSsl = "X-Forwarded-Ssl"
- HeaderXUrlScheme = "X-Url-Scheme"
- HeaderLocation = "Location"
- HeaderFrom = "From"
- HeaderHost = "Host"
- HeaderReferer = "Referer"
- HeaderReferrerPolicy = "Referrer-Policy"
- HeaderUserAgent = "User-Agent"
- HeaderAllow = "Allow"
- HeaderServer = "Server"
- HeaderAcceptRanges = "Accept-Ranges"
- HeaderContentRange = "Content-Range"
- HeaderIfRange = "If-Range"
- HeaderRange = "Range"
- HeaderContentSecurityPolicy = "Content-Security-Policy"
- HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
- HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
- HeaderExpectCT = "Expect-CT"
- HeaderFeaturePolicy = "Feature-Policy"
- HeaderPublicKeyPins = "Public-Key-Pins"
- HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
- HeaderStrictTransportSecurity = "Strict-Transport-Security"
- HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
- HeaderXContentTypeOptions = "X-Content-Type-Options"
- HeaderXDownloadOptions = "X-Download-Options"
- HeaderXFrameOptions = "X-Frame-Options"
- HeaderXPoweredBy = "X-Powered-By"
- HeaderXXSSProtection = "X-XSS-Protection"
- HeaderLastEventID = "Last-Event-ID"
- HeaderNEL = "NEL"
- HeaderPingFrom = "Ping-From"
- HeaderPingTo = "Ping-To"
- HeaderReportTo = "Report-To"
- HeaderTE = "TE"
- HeaderTrailer = "Trailer"
- HeaderTransferEncoding = "Transfer-Encoding"
- HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
- HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
- HeaderSecWebSocketKey = "Sec-WebSocket-Key"
- HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
- HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
- HeaderAcceptPatch = "Accept-Patch"
- HeaderAcceptPushPolicy = "Accept-Push-Policy"
- HeaderAcceptSignature = "Accept-Signature"
- HeaderAltSvc = "Alt-Svc"
- HeaderDate = "Date"
- HeaderIndex = "Index"
- HeaderLargeAllocation = "Large-Allocation"
- HeaderLink = "Link"
- HeaderPushPolicy = "Push-Policy"
- HeaderRetryAfter = "Retry-After"
- HeaderServerTiming = "Server-Timing"
- HeaderSignature = "Signature"
- HeaderSignedHeaders = "Signed-Headers"
- HeaderSourceMap = "SourceMap"
- HeaderUpgrade = "Upgrade"
- HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
- HeaderXPingback = "X-Pingback"
- HeaderXRequestID = "X-Request-ID"
- HeaderXRequestedWith = "X-Requested-With"
- HeaderXRobotsTag = "X-Robots-Tag"
- HeaderXUACompatible = "X-UA-Compatible"
+ HeaderAuthorization = "Authorization"
+ HeaderProxyAuthenticate = "Proxy-Authenticate"
+ HeaderProxyAuthorization = "Proxy-Authorization"
+ HeaderWWWAuthenticate = "WWW-Authenticate"
+ HeaderAge = "Age"
+ HeaderCacheControl = "Cache-Control"
+ HeaderClearSiteData = "Clear-Site-Data"
+ HeaderExpires = "Expires"
+ HeaderPragma = "Pragma"
+ HeaderWarning = "Warning"
+ HeaderAcceptCH = "Accept-CH"
+ HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
+ HeaderContentDPR = "Content-DPR"
+ HeaderDPR = "DPR"
+ HeaderEarlyData = "Early-Data"
+ HeaderSaveData = "Save-Data"
+ HeaderViewportWidth = "Viewport-Width"
+ HeaderWidth = "Width"
+ HeaderETag = "ETag"
+ HeaderIfMatch = "If-Match"
+ HeaderIfModifiedSince = "If-Modified-Since"
+ HeaderIfNoneMatch = "If-None-Match"
+ HeaderIfUnmodifiedSince = "If-Unmodified-Since"
+ HeaderLastModified = "Last-Modified"
+ HeaderVary = "Vary"
+ HeaderConnection = "Connection"
+ HeaderKeepAlive = "Keep-Alive"
+ HeaderAccept = "Accept"
+ HeaderAcceptCharset = "Accept-Charset"
+ HeaderAcceptEncoding = "Accept-Encoding"
+ HeaderAcceptLanguage = "Accept-Language"
+ HeaderCookie = "Cookie"
+ HeaderExpect = "Expect"
+ HeaderMaxForwards = "Max-Forwards"
+ HeaderSetCookie = "Set-Cookie"
+ HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
+ HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
+ HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
+ HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
+ HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
+ HeaderAccessControlMaxAge = "Access-Control-Max-Age"
+ HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
+ HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
+ HeaderOrigin = "Origin"
+ HeaderTimingAllowOrigin = "Timing-Allow-Origin"
+ HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
+ HeaderDNT = "DNT"
+ HeaderTk = "Tk"
+ HeaderContentDisposition = "Content-Disposition"
+ HeaderContentEncoding = "Content-Encoding"
+ HeaderContentLanguage = "Content-Language"
+ HeaderContentLength = "Content-Length"
+ HeaderContentLocation = "Content-Location"
+ HeaderContentType = "Content-Type"
+ HeaderForwarded = "Forwarded"
+ HeaderVia = "Via"
+ HeaderXForwardedFor = "X-Forwarded-For"
+ HeaderXForwardedHost = "X-Forwarded-Host"
+ HeaderXForwardedProto = "X-Forwarded-Proto"
+ HeaderXForwardedProtocol = "X-Forwarded-Protocol"
+ HeaderXForwardedSsl = "X-Forwarded-Ssl"
+ HeaderXUrlScheme = "X-Url-Scheme"
+ HeaderLocation = "Location"
+ HeaderFrom = "From"
+ HeaderHost = "Host"
+ HeaderReferer = "Referer"
+ HeaderReferrerPolicy = "Referrer-Policy"
+ HeaderUserAgent = "User-Agent"
+ HeaderAllow = "Allow"
+ HeaderServer = "Server"
+ HeaderAcceptRanges = "Accept-Ranges"
+ HeaderContentRange = "Content-Range"
+ HeaderIfRange = "If-Range"
+ HeaderRange = "Range"
+ HeaderContentSecurityPolicy = "Content-Security-Policy"
+ HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
+ HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
+ HeaderExpectCT = "Expect-CT"
+ HeaderFeaturePolicy = "Feature-Policy"
+ HeaderPublicKeyPins = "Public-Key-Pins"
+ HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
+ HeaderStrictTransportSecurity = "Strict-Transport-Security"
+ HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
+ HeaderXContentTypeOptions = "X-Content-Type-Options"
+ HeaderXDownloadOptions = "X-Download-Options"
+ HeaderXFrameOptions = "X-Frame-Options"
+ HeaderXPoweredBy = "X-Powered-By"
+ HeaderXXSSProtection = "X-XSS-Protection"
+ HeaderLastEventID = "Last-Event-ID"
+ HeaderNEL = "NEL"
+ HeaderPingFrom = "Ping-From"
+ HeaderPingTo = "Ping-To"
+ HeaderReportTo = "Report-To"
+ HeaderTE = "TE"
+ HeaderTrailer = "Trailer"
+ HeaderTransferEncoding = "Transfer-Encoding"
+ HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
+ HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
+ HeaderSecWebSocketKey = "Sec-WebSocket-Key"
+ HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
+ HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
+ HeaderAcceptPatch = "Accept-Patch"
+ HeaderAcceptPushPolicy = "Accept-Push-Policy"
+ HeaderAcceptSignature = "Accept-Signature"
+ HeaderAltSvc = "Alt-Svc"
+ HeaderDate = "Date"
+ HeaderIndex = "Index"
+ HeaderLargeAllocation = "Large-Allocation"
+ HeaderLink = "Link"
+ HeaderPushPolicy = "Push-Policy"
+ HeaderRetryAfter = "Retry-After"
+ HeaderServerTiming = "Server-Timing"
+ HeaderSignature = "Signature"
+ HeaderSignedHeaders = "Signed-Headers"
+ HeaderSourceMap = "SourceMap"
+ HeaderUpgrade = "Upgrade"
+ HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
+ HeaderXPingback = "X-Pingback"
+ HeaderXRequestID = "X-Request-ID"
+ HeaderXRequestedWith = "X-Requested-With"
+ HeaderXRobotsTag = "X-Robots-Tag"
+ HeaderXUACompatible = "X-UA-Compatible"
)
```
diff --git a/docs/api/ctx.md b/docs/api/ctx.md
index 0434671c..2640512a 100644
--- a/docs/api/ctx.md
+++ b/docs/api/ctx.md
@@ -165,7 +165,6 @@ Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US
The supported content types are `text/html`, `text/plain`, `application/json`, and `application/xml`.
For more flexible content negotiation, use [Format](ctx.md#format).
-
:::info
If the header is **not** specified or there is **no** proper format, **text/plain** is used.
:::
@@ -375,17 +374,17 @@ func (c Ctx) Cookie(cookie *Cookie)
```go
type Cookie struct {
- Name string `json:"name"` // The name of the cookie
- Value string `json:"value"` // The value of the cookie
- Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
- Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
- MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
- Expires time.Time `json:"expires"` // The expiration date of the cookie
- Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
- HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
- SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
- Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
- SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
+ Name string `json:"name"` // The name of the cookie
+ Value string `json:"value"` // The value of the cookie
+ Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
+ Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
+ MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
+ Expires time.Time `json:"expires"` // The expiration date of the cookie
+ Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
+ HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
+ SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
+ Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
+ SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
}
```
@@ -1001,7 +1000,7 @@ app.Get("/admin", func(c fiber.Ctx) error {
})
```
-An alternative version of the Locals method that takes advantage of Go's generics feature is also available. This version
+An alternative version of the Locals method that takes advantage of Go's generics feature is also available. This version
allows for the manipulation and retrieval of local values within a request's context with a more specific data type.
```go title="Signature"
@@ -1023,7 +1022,7 @@ app.Get("/test", func(c Ctx) error {
})
````
-Make sure to understand and correctly implement the Locals method in both its standard and generic form for better control
+Make sure to understand and correctly implement the Locals method in both its standard and generic form for better control
over route-specific data within your application.
## Location
@@ -1206,9 +1205,8 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more..
:::
-
-In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not
-just strings. This can be achieved using a generic Query function known as `Params[V GenericType](c Ctx, key string, defaultValue ...V) V`.
+In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not
+just strings. This can be achieved using a generic Query function known as `Params[V GenericType](c Ctx, key string, defaultValue ...V) V`.
This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`.
```go title="Signature"
@@ -1227,6 +1225,7 @@ app.Get("/user/:id", func(c fiber.Ctx) error{
```
The generic Params function supports returning the following data types based on V GenericType:
+
- Integer: int, int8, int16, int32, int64
- Unsigned integer: uint, uint8, uint16, uint32, uint64
- Floating-point numbers: float32, float64
@@ -1234,7 +1233,6 @@ The generic Params function supports returning the following data types based on
- String: string
- Byte array: []byte
-
## Path
Contains the path part of the request URL. Optionally, you could override the path by passing a string. For internal redirects, you might want to call [RestartRouting](ctx.md#restartrouting) instead of [Next](ctx.md#next).
@@ -1303,11 +1301,11 @@ func (c Ctx) Queries() map[string]string
// GET http://example.com/?name=alex&want_pizza=false&id=
app.Get("/", func(c fiber.Ctx) error {
- m := c.Queries()
- m["name"] // "alex"
- m["want_pizza"] // "false"
- m["id"] // ""
- // ...
+ m := c.Queries()
+ m["name"] // "alex"
+ m["want_pizza"] // "false"
+ m["id"] // ""
+ // ...
})
```
@@ -1315,9 +1313,9 @@ app.Get("/", func(c fiber.Ctx) error {
// GET http://example.com/?field1=value1&field1=value2&field2=value3
app.Get("/", func (c fiber.Ctx) error {
- m := c.Queries()
- m["field1"] // "value2"
- m["field2"] // value3
+ m := c.Queries()
+ m["field1"] // "value2"
+ m["field2"] // value3
})
```
@@ -1325,10 +1323,10 @@ app.Get("/", func (c fiber.Ctx) error {
// GET http://example.com/?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3
app.Get("/", func(c fiber.Ctx) error {
- m := c.Queries()
- m["list_a"] // "3"
- m["list_b[]"] // "3"
- m["list_c"] // "1,2,3"
+ m := c.Queries()
+ m["list_a"] // "3"
+ m["list_b[]"] // "3"
+ m["list_c"] // "1,2,3"
})
```
@@ -1336,9 +1334,9 @@ app.Get("/", func(c fiber.Ctx) error {
// GET /api/posts?filters.author.name=John&filters.category.name=Technology
app.Get("/", func(c fiber.Ctx) error {
- m := c.Queries()
- m["filters.author.name"] // John
- m["filters.category.name"] // Technology
+ m := c.Queries()
+ m["filters.author.name"] // John
+ m["filters.category.name"] // Technology
})
```
@@ -1346,12 +1344,12 @@ app.Get("/", func(c fiber.Ctx) error {
// GET /api/posts?tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits
app.Get("/", func(c fiber.Ctx) error {
- m := c.Queries()
- m["tags"] // apple,orange,banana
- m["filters[tags]"] // apple,orange,banana
- m["filters[category][name]"] // fruits
- m["filters.tags"] // apple,orange,banana
- m["filters.category.name"] // fruits
+ m := c.Queries()
+ m["tags"] // apple,orange,banana
+ m["filters[tags]"] // apple,orange,banana
+ m["filters[category][name]"] // fruits
+ m["filters.tags"] // apple,orange,banana
+ m["filters.category.name"] // fruits
})
```
@@ -1386,8 +1384,8 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more..
:::
-In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not
-just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`.
+In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not
+just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`.
This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`.
Here is the signature for the generic Query function:
@@ -1410,12 +1408,13 @@ app.Get("/", func(c fiber.Ctx) error {
})
```
-In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand'
-as a string, and 'new' as a boolean. The function uses the appropriate parsing function for each specified type to ensure
-the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your
+In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand'
+as a string, and 'new' as a boolean. The function uses the appropriate parsing function for each specified type to ensure
+the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your
controller actions cleaner.
The generic Query function supports returning the following data types based on V GenericType:
+
- Integer: int, int8, int16, int32, int64
- Unsigned integer: uint, uint8, uint16, uint32, uint64
- Floating-point numbers: float32, float64
@@ -1464,7 +1463,6 @@ app.Get("/teapot", func(c fiber.Ctx) error {
})
```
-
## Render
Renders a view with data and sends a `text/html` response. By default `Render` uses the default [**Go Template engine**](https://pkg.go.dev/html/template/). If you want to use another View engine, please take a look at our [**Template middleware**](https://docs.gofiber.io/template).
@@ -1746,7 +1744,7 @@ type SendFile struct {
// The value for the Cache-Control HTTP-header
// that is set on the file response. MaxAge is defined in seconds.
//
- // Optional. Default value 0.
+ // Optional. Default value 0.
MaxAge int `json:"max_age"`
}
```
diff --git a/docs/api/fiber.md b/docs/api/fiber.md
index 08d8b6ae..16f23b9e 100644
--- a/docs/api/fiber.md
+++ b/docs/api/fiber.md
@@ -82,7 +82,6 @@ app := fiber.New(fiber.Config{
| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` |
| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` |
-
## Server listening
### Config
@@ -112,10 +111,9 @@ app.Listen(":8080", fiber.ListenConfig{
| ListenerAddrFunc | `func(addr net.Addr)` | Allows accessing and customizing `net.Listener`. | `nil` |
| ListenerNetwork | `string` | Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only). WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. | `tcp4` |
| OnShutdownError | `func(err error)` | Allows to customize error behavior when gracefully shutting down the server by given signal. Prints error with `log.Fatalf()` | `nil` |
-| OnShutdownSuccess | `func()` | Allows to customize success behavior when gracefully shutting down the server by given signal. | `nil` |
+| OnShutdownSuccess | `func()` | Allows customizing success behavior when gracefully shutting down the server by given signal. | `nil` |
| TLSConfigFunc | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. | `nil` |
-
### Listen
Listen serves HTTP requests from the given address.
@@ -215,7 +213,6 @@ func (app *App) ShutdownWithTimeout(timeout time.Duration) error
func (app *App) ShutdownWithContext(ctx context.Context) error
```
-
## Helper functions
### NewError
diff --git a/docs/api/hooks.md b/docs/api/hooks.md
index 8717ba58..2ef3a461 100644
--- a/docs/api/hooks.md
+++ b/docs/api/hooks.md
@@ -7,7 +7,8 @@ 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 this hooks:
+With Fiber v2.30.0, you can execute custom user functions when to run some methods. Here is a list of these hooks:
+
- [OnRoute](#onroute)
- [OnName](#onname)
- [OnGroup](#ongroup)
@@ -18,6 +19,7 @@ With Fiber v2.30.0, you can execute custom user functions when to run some metho
- [OnMount](#onmount)
## Constants
+
```go
// Handlers define a function to create hooks for Fiber.
type OnRouteHandler = func(Route) error
@@ -57,45 +59,46 @@ func (h *Hooks) OnName(handler ...OnNameHandler)
package main
import (
- "fmt"
+ "fmt"
- "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3"
)
func main() {
- app := fiber.New()
+ app := fiber.New()
- app.Get("/", func(c fiber.Ctx) error {
- return c.SendString(c.Route().Name)
- }).Name("index")
+ app.Get("/", func(c fiber.Ctx) error {
+ return c.SendString(c.Route().Name)
+ }).Name("index")
- app.Hooks().OnName(func(r fiber.Route) error {
- fmt.Print("Name: " + r.Name + ", ")
+ app.Hooks().OnName(func(r fiber.Route) error {
+ fmt.Print("Name: " + r.Name + ", ")
- return nil
- })
+ return nil
+ })
- app.Hooks().OnName(func(r fiber.Route) error {
- fmt.Print("Method: " + r.Method + "\n")
+ app.Hooks().OnName(func(r fiber.Route) error {
+ fmt.Print("Method: " + r.Method + "\n")
- return nil
- })
+ return nil
+ })
- app.Get("/add/user", func(c fiber.Ctx) error {
- return c.SendString(c.Route().Name)
- }).Name("addUser")
+ app.Get("/add/user", func(c fiber.Ctx) error {
+ return c.SendString(c.Route().Name)
+ }).Name("addUser")
- app.Delete("/destroy/user", func(c fiber.Ctx) error {
- return c.SendString(c.Route().Name)
- }).Name("destroyUser")
+ app.Delete("/destroy/user", func(c fiber.Ctx) error {
+ return c.SendString(c.Route().Name)
+ }).Name("destroyUser")
- app.Listen(":5000")
+ app.Listen(":5000")
}
// Results:
// Name: addUser, Method: GET
// Name: destroyUser, Method: DELETE
```
+
@@ -137,7 +140,7 @@ app := fiber.New(fiber.Config{
app.Hooks().OnListen(func(listenData fiber.ListenData) error {
if fiber.IsChild() {
- return nil
+ return nil
}
scheme := "http"
if data.TLS {
@@ -184,26 +187,26 @@ func (h *Hooks) OnMount(handler ...OnMountHandler)
package main
import (
- "fmt"
+ "fmt"
- "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3"
)
func main() {
- app := New()
- app.Get("/", testSimpleHandler).Name("x")
+ app := New()
+ app.Get("/", testSimpleHandler).Name("x")
- subApp := New()
- subApp.Get("/test", testSimpleHandler)
+ subApp := New()
+ subApp.Get("/test", testSimpleHandler)
- subApp.Hooks().OnMount(func(parent *fiber.App) error {
- fmt.Print("Mount path of parent app: "+parent.MountPath())
- // ...
+ subApp.Hooks().OnMount(func(parent *fiber.App) error {
+ fmt.Print("Mount path of parent app: "+parent.MountPath())
+ // ...
- return nil
- })
+ return nil
+ })
- app.Mount("/sub", subApp)
+ app.Mount("/sub", subApp)
}
// Result:
@@ -213,6 +216,5 @@ func main() {
-
:::caution
-OnName/OnRoute/OnGroup/OnGroupName hooks are mount-sensitive. If you use one of these routes on sub app and you mount it; paths of routes and groups will start with mount prefix.
+OnName/OnRoute/OnGroup/OnGroupName hooks are mount-sensitive. If you use one of these routes on sub app, and you mount it; paths of routes and groups will start with mount prefix.
diff --git a/docs/api/log.md b/docs/api/log.md
index 9289967d..508be9a6 100644
--- a/docs/api/log.md
+++ b/docs/api/log.md
@@ -13,13 +13,13 @@ Fiber offers a default mechanism for logging to standard output. Additionally, i
```go
const (
- LevelTrace Level = iota
- LevelDebug
- LevelInfo
- LevelWarn
- LevelError
- LevelFatal
- LevelPanic
+ LevelTrace Level = iota
+ LevelDebug
+ LevelInfo
+ LevelWarn
+ LevelError
+ LevelFatal
+ LevelPanic
)
```
@@ -42,9 +42,11 @@ type AllLogger interface {
```
## Print log
+
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`.
```go
@@ -58,6 +60,7 @@ log.Panic("The system is down.")
```
### Formatted Logging
+
Logs of different levels can be formatted before printing. All such methods end with an `f`.
```go
@@ -69,6 +72,7 @@ log.Fatalf("So Long, and Thanks for All the %s.", "banana")
```
### Key-Value Logging
+
Print a message with key-value pairs. If the key and value are not paired correctly, the log will output `KEYVALS UNPAIRED`.
```go
@@ -80,6 +84,7 @@ log.Fatalw("", "fruit", "banana")
```
## Global log
+
For projects that require a simple, global logging function to print messages at any time, Fiber provides a global log.
```go
@@ -113,6 +118,7 @@ fiberlog.SetLogger(customLogger)
```
## 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**.
diff --git a/docs/api/redirect.md b/docs/api/redirect.md
index e1403b73..79faa36d 100644
--- a/docs/api/redirect.md
+++ b/docs/api/redirect.md
@@ -190,7 +190,6 @@ app.Get("/", func(c fiber.Ctx) error {
})
```
-
#### OldInputs
Get old input data. Check [WithInput](#withinput) for more information.
diff --git a/docs/client/examples.md b/docs/client/examples.md
index 2ea79609..86e73b65 100644
--- a/docs/client/examples.md
+++ b/docs/client/examples.md
@@ -18,28 +18,29 @@ import TabItem from '@theme/TabItem';
package main
import (
- "encoding/base64"
- "fmt"
+ "encoding/base64"
+ "fmt"
- "github.com/gofiber/fiber/v3/client"
+ "github.com/gofiber/fiber/v3/client"
)
func main() {
- cc := client.New()
+ cc := client.New()
- out := base64.StdEncoding.EncodeToString([]byte("john:doe"))
- resp, err := cc.Get("http://localhost:3000", client.Config{
- Header: map[string]string{
- "Authorization": "Basic " + out,
- },
- })
- if err != nil {
- panic(err)
- }
+ out := base64.StdEncoding.EncodeToString([]byte("john:doe"))
+ resp, err := cc.Get("http://localhost:3000", client.Config{
+ Header: map[string]string{
+ "Authorization": "Basic " + out,
+ },
+ })
+ if err != nil {
+ panic(err)
+ }
- fmt.Print(string(resp.Body()))
+ fmt.Print(string(resp.Body()))
}
```
+
@@ -47,27 +48,28 @@ func main() {
package main
import (
- "github.com/gofiber/fiber/v3"
- "github.com/gofiber/fiber/v3/middleware/basicauth"
+ "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3/middleware/basicauth"
)
func main() {
- app := fiber.New()
- app.Use(
- basicauth.New(basicauth.Config{
- Users: map[string]string{
- "john": "doe",
- },
- }),
- )
+ app := fiber.New()
+ app.Use(
+ basicauth.New(basicauth.Config{
+ Users: map[string]string{
+ "john": "doe",
+ },
+ }),
+ )
- app.Get("/", func(c fiber.Ctx) error {
- return c.SendString("Hello, World!")
- })
+ app.Get("/", func(c fiber.Ctx) error {
+ return c.SendString("Hello, World!")
+ })
- app.Listen(":3000")
+ app.Listen(":3000")
}
```
+
@@ -80,40 +82,41 @@ func main() {
package main
import (
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "os"
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "os"
- "github.com/gofiber/fiber/v3/client"
+ "github.com/gofiber/fiber/v3/client"
)
func main() {
- cc := client.New()
+ cc := client.New()
- certPool, err := x509.SystemCertPool()
- if err != nil {
- panic(err)
- }
+ certPool, err := x509.SystemCertPool()
+ if err != nil {
+ panic(err)
+ }
- cert, err := os.ReadFile("ssl.cert")
- if err != nil {
- panic(err)
- }
+ cert, err := os.ReadFile("ssl.cert")
+ if err != nil {
+ panic(err)
+ }
- certPool.AppendCertsFromPEM(cert)
- cc.SetTLSConfig(&tls.Config{
- RootCAs: certPool,
- })
+ certPool.AppendCertsFromPEM(cert)
+ cc.SetTLSConfig(&tls.Config{
+ RootCAs: certPool,
+ })
- resp, err := cc.Get("https://localhost:3000")
- if err != nil {
- panic(err)
- }
+ resp, err := cc.Get("https://localhost:3000")
+ if err != nil {
+ panic(err)
+ }
- fmt.Print(string(resp.Body()))
+ fmt.Print(string(resp.Body()))
}
```
+
@@ -121,25 +124,26 @@ func main() {
package main
import (
- "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3"
)
func main() {
- app := fiber.New()
+ app := fiber.New()
- app.Get("/", func(c fiber.Ctx) error {
- return c.SendString("Hello, World!")
- })
+ app.Get("/", func(c fiber.Ctx) error {
+ return c.SendString("Hello, World!")
+ })
- err := app.Listen(":3000", fiber.ListenConfig{
- CertFile: "ssl.cert",
- CertKeyFile: "ssl.key",
- })
- if err != nil {
- panic(err)
- }
+ err := app.Listen(":3000", fiber.ListenConfig{
+ CertFile: "ssl.cert",
+ CertKeyFile: "ssl.key",
+ })
+ if err != nil {
+ panic(err)
+ }
}
```
+
@@ -149,20 +153,20 @@ func main() {
```go
func main() {
- jar := client.AcquireCookieJar()
- defer client.ReleaseCookieJar(jar)
+ jar := client.AcquireCookieJar()
+ defer client.ReleaseCookieJar(jar)
- cc := client.New()
- cc.SetCookieJar(jar)
+ cc := client.New()
+ cc.SetCookieJar(jar)
- jar.SetKeyValueBytes("httpbin.org", []byte("john"), []byte("doe"))
+ jar.SetKeyValueBytes("httpbin.org", []byte("john"), []byte("doe"))
- resp, err := cc.Get("https://httpbin.org/cookies")
- if err != nil {
- panic(err)
- }
+ resp, err := cc.Get("https://httpbin.org/cookies")
+ if err != nil {
+ panic(err)
+ }
- fmt.Println(string(resp.Body()))
+ fmt.Println(string(resp.Body()))
}
```
@@ -183,23 +187,23 @@ func main() {
```go
func main() {
- jar := client.AcquireCookieJar()
- defer client.ReleaseCookieJar(jar)
+ jar := client.AcquireCookieJar()
+ defer client.ReleaseCookieJar(jar)
- cc := client.New()
- cc.SetCookieJar(jar)
+ cc := client.New()
+ cc.SetCookieJar(jar)
- _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
+ if err != nil {
+ panic(err)
+ }
- uri := fasthttp.AcquireURI()
- defer fasthttp.ReleaseURI(uri)
+ uri := fasthttp.AcquireURI()
+ defer fasthttp.ReleaseURI(uri)
- uri.SetHost("httpbin.org")
- uri.SetPath("/cookies")
- fmt.Println(jar.Get(uri))
+ uri.SetHost("httpbin.org")
+ uri.SetPath("/cookies")
+ fmt.Println(jar.Get(uri))
}
```
@@ -216,23 +220,23 @@ func main() {
```go
func main() {
- jar := client.AcquireCookieJar()
- defer client.ReleaseCookieJar(jar)
+ jar := client.AcquireCookieJar()
+ defer client.ReleaseCookieJar(jar)
- cc := client.New()
- cc.SetCookieJar(jar)
+ cc := client.New()
+ cc.SetCookieJar(jar)
- _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
+ if err != nil {
+ panic(err)
+ }
- resp, err := cc.Get("https://httpbin.org/cookies")
- if err != nil {
- panic(err)
- }
+ resp, err := cc.Get("https://httpbin.org/cookies")
+ if err != nil {
+ panic(err)
+ }
- fmt.Println(resp.String())
+ fmt.Println(resp.String())
}
```
diff --git a/docs/client/hooks.md b/docs/client/hooks.md
index 3ae510f3..8616eac7 100644
--- a/docs/client/hooks.md
+++ b/docs/client/hooks.md
@@ -18,43 +18,43 @@ You need to use `RequestHook func(*Client, *Request) error` function signature w
```go
type Repository struct {
- Name string `json:"name"`
- FullName string `json:"full_name"`
- Description string `json:"description"`
- Homepage string `json:"homepage"`
+ Name string `json:"name"`
+ FullName string `json:"full_name"`
+ Description string `json:"description"`
+ Homepage string `json:"homepage"`
- Owner struct {
- Login string `json:"login"`
- } `json:"owner"`
+ Owner struct {
+ Login string `json:"login"`
+ } `json:"owner"`
}
func main() {
- cc := client.New()
+ cc := client.New()
- cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
- r.SetURL("https://api.github.com/" + r.URL())
+ cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
+ r.SetURL("https://api.github.com/" + r.URL())
- return nil
- })
+ return nil
+ })
- resp, err := cc.Get("repos/gofiber/fiber")
- if err != nil {
- panic(err)
- }
+ resp, err := cc.Get("repos/gofiber/fiber")
+ if err != nil {
+ panic(err)
+ }
- var repo Repository
- if err := resp.JSON(&repo); err != nil {
- panic(err)
- }
+ var repo Repository
+ if err := resp.JSON(&repo); err != nil {
+ panic(err)
+ }
- fmt.Printf("Status code: %d\n", resp.StatusCode())
+ fmt.Printf("Status code: %d\n", resp.StatusCode())
- fmt.Printf("Repository: %s\n", repo.FullName)
- fmt.Printf("Description: %s\n", repo.Description)
- fmt.Printf("Homepage: %s\n", repo.Homepage)
- fmt.Printf("Owner: %s\n", repo.Owner.Login)
- fmt.Printf("Name: %s\n", repo.Name)
- fmt.Printf("Full Name: %s\n", repo.FullName)
+ fmt.Printf("Repository: %s\n", repo.FullName)
+ fmt.Printf("Description: %s\n", repo.Description)
+ fmt.Printf("Homepage: %s\n", repo.Homepage)
+ fmt.Printf("Owner: %s\n", repo.Owner.Login)
+ fmt.Printf("Name: %s\n", repo.Name)
+ fmt.Printf("Full Name: %s\n", repo.FullName)
}
```
@@ -87,22 +87,22 @@ If any error returns from request hook execution, it will interrupt the request
```go
func main() {
- cc := client.New()
+ cc := client.New()
- cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
- fmt.Println("Hook 1")
- return errors.New("error")
- })
+ cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
+ fmt.Println("Hook 1")
+ return errors.New("error")
+ })
- cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
- fmt.Println("Hook 2")
- return nil
- })
+ cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
+ fmt.Println("Hook 2")
+ return nil
+ })
- _, err := cc.Get("https://example.com/")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://example.com/")
+ if err != nil {
+ panic(err)
+ }
}
```
@@ -129,24 +129,24 @@ You need to use `ResponseHook func(*Client, *Response, *Request) error` function
```go
func main() {
- cc := client.New()
+ cc := client.New()
- cc.AddResponseHook(func(c *client.Client, resp *client.Response, req *client.Request) error {
- fmt.Printf("Response Status Code: %d\n", resp.StatusCode())
- fmt.Printf("HTTP protocol: %s\n\n", resp.Protocol())
+ cc.AddResponseHook(func(c *client.Client, resp *client.Response, req *client.Request) error {
+ fmt.Printf("Response Status Code: %d\n", resp.StatusCode())
+ fmt.Printf("HTTP protocol: %s\n\n", resp.Protocol())
- fmt.Println("Response Headers:")
- resp.RawResponse.Header.VisitAll(func(key, value []byte) {
- fmt.Printf("%s: %s\n", key, value)
- })
+ fmt.Println("Response Headers:")
+ resp.RawResponse.Header.VisitAll(func(key, value []byte) {
+ fmt.Printf("%s: %s\n", key, value)
+ })
- return nil
- })
+ return nil
+ })
- _, err := cc.Get("https://example.com/")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://example.com/")
+ if err != nil {
+ panic(err)
+ }
}
```
@@ -185,27 +185,27 @@ If any error is returned from executing the response hook, it will return the er
```go
func main() {
- cc := client.New()
+ cc := client.New()
- cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
- fmt.Println("Hook 1")
- return nil
- })
+ cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
+ fmt.Println("Hook 1")
+ return nil
+ })
- cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
- fmt.Println("Hook 2")
- return errors.New("error")
- })
+ cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
+ fmt.Println("Hook 2")
+ return errors.New("error")
+ })
- cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
- fmt.Println("Hook 3")
- return nil
- })
+ cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error {
+ fmt.Println("Hook 3")
+ return nil
+ })
- _, err := cc.Get("https://example.com/")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://example.com/")
+ if err != nil {
+ panic(err)
+ }
}
```
@@ -231,22 +231,22 @@ Hooks work as FIFO (first-in-first-out). You need to check the order while addin
```go
func main() {
- cc := client.New()
+ cc := client.New()
- cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
- fmt.Println("Hook 1")
- return nil
- })
+ cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
+ fmt.Println("Hook 1")
+ return nil
+ })
- cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
- fmt.Println("Hook 2")
- return nil
- })
+ cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
+ fmt.Println("Hook 2")
+ return nil
+ })
- _, err := cc.Get("https://example.com/")
- if err != nil {
- panic(err)
- }
+ _, err := cc.Get("https://example.com/")
+ if err != nil {
+ panic(err)
+ }
}
```
diff --git a/docs/client/request.md b/docs/client/request.md
index a5a025bd..d1126dae 100644
--- a/docs/client/request.md
+++ b/docs/client/request.md
@@ -18,32 +18,31 @@ This structure is designed to be flexible and efficient, allowing users to easil
```go
type Request struct {
- url string
- method string
- userAgent string
- boundary string
- referer string
- ctx context.Context
- header *Header
- params *QueryParam
- cookies *Cookie
- path *PathParam
+ url string
+ method string
+ userAgent string
+ boundary string
+ referer string
+ ctx context.Context
+ header *Header
+ params *QueryParam
+ cookies *Cookie
+ path *PathParam
- timeout time.Duration
- maxRedirects int
+ timeout time.Duration
+ maxRedirects int
- client *Client
+ client *Client
- body any
- formData *FormData
- files []*File
- bodyType bodyType
+ body any
+ formData *FormData
+ files []*File
+ bodyType bodyType
- RawRequest *fasthttp.Request
+ RawRequest *fasthttp.Request
}
```
-
## REST Methods
### Get
@@ -207,6 +206,7 @@ func (r *Request) SetContext(ctx context.Context) *Request
## Header
Header method returns header value via key, this method will visit all field in the header.
+
```go title="Signature"
func (r *Request) Header(key string) []string
```
@@ -229,7 +229,7 @@ req.AddHeader("Test", "654321")
resp, err := req.Get("https://httpbin.org/headers")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(resp.String())
@@ -271,7 +271,7 @@ req.SetHeader("Test", "654321")
resp, err := req.Get("https://httpbin.org/headers")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(resp.String())
@@ -338,7 +338,7 @@ req.AddParam("hobbies", "basketball")
resp, err := req.Get("https://httpbin.org/response-headers")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -401,19 +401,19 @@ req := client.AcquireRequest()
defer client.ReleaseRequest(req)
req.SetParamsWithStruct(struct {
- Name string `json:"name"`
- Hobbies []string `json:"hobbies"`
+ Name string `json:"name"`
+ Hobbies []string `json:"hobbies"`
}{
- Name: "John Doe",
- Hobbies: []string{
- "Football",
- "Basketball",
- },
+ Name: "John Doe",
+ Hobbies: []string{
+ "Football",
+ "Basketball",
+ },
})
resp, err := req.Get("https://httpbin.org/response-headers")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -525,13 +525,13 @@ req := client.AcquireRequest()
defer client.ReleaseRequest(req)
req.SetCookies(map[string]string{
- "cookie1": "value1",
- "cookie2": "value2",
+ "cookie1": "value1",
+ "cookie2": "value2",
})
resp, err := req.Get("https://httpbin.org/cookies")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -592,7 +592,7 @@ req.SetPathParam("base64", "R29maWJlcg==")
resp, err := req.Get("https://httpbin.org/base64/:base64")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -691,7 +691,7 @@ req.AddFormData("points", "100")
resp, err := req.Post("https://httpbin.org/post")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -735,7 +735,7 @@ req.SetFormData("email", "john@doe.com")
resp, err := req.Post("https://httpbin.org/post")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -832,7 +832,7 @@ req.AddFile("test.txt")
resp, err := req.Post("https://httpbin.org/post")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -872,7 +872,7 @@ req.AddFileWithReader("test.txt", io.NopCloser(buf))
resp, err := req.Post("https://httpbin.org/post")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -928,7 +928,7 @@ req.SetTimeout(5 * time.Second)
resp, err := req.Get("https://httpbin.org/delay/4")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -957,7 +957,7 @@ req.SetTimeout(5 * time.Second)
resp, err := req.Get("https://httpbin.org/delay/6")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -1016,7 +1016,7 @@ Header is a wrapper which wrap http.Header, the header in client and request wil
```go
type Header struct {
- *fasthttp.RequestHeader
+ *fasthttp.RequestHeader
}
```
@@ -1050,7 +1050,7 @@ QueryParam is a wrapper which wrap url.Values, the query string and formdata in
```go
type QueryParam struct {
- *fasthttp.Args
+ *fasthttp.Args
}
```
@@ -1229,7 +1229,7 @@ FormData is a wrapper of fasthttp.Args and it is used for url encode body and fi
```go
type FormData struct {
- *fasthttp.Args
+ *fasthttp.Args
}
```
@@ -1295,10 +1295,10 @@ File is a struct which support send files via request.
```go
type File struct {
- name string
- fieldName string
- path string
- reader io.ReadCloser
+ name string
+ fieldName string
+ path string
+ reader io.ReadCloser
}
```
diff --git a/docs/client/response.md b/docs/client/response.md
index 85ea79c4..2256d400 100644
--- a/docs/client/response.md
+++ b/docs/client/response.md
@@ -17,11 +17,11 @@ This structure allows users to easily access and manage the data returned by the
```go
type Response struct {
- client *Client
- request *Request
- cookie []*fasthttp.Cookie
+ client *Client
+ request *Request
+ cookie []*fasthttp.Cookie
- RawResponse *fasthttp.Response
+ RawResponse *fasthttp.Response
}
```
@@ -71,7 +71,7 @@ func (r *Response) Protocol() string
```go title="Example"
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(resp.Protocol())
@@ -80,7 +80,7 @@ fmt.Println(resp.Protocol())
Click here to see the result
-```
+```text
HTTP/1.1
```
@@ -105,19 +105,19 @@ func (r *Response) Cookies() []*fasthttp.Cookie
```go title="Example"
resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber")
if err != nil {
- panic(err)
+ panic(err)
}
cookies := resp.Cookies()
for _, cookie := range cookies {
- fmt.Printf("%s => %s\n", string(cookie.Key()), string(cookie.Value()))
+ fmt.Printf("%s => %s\n", string(cookie.Key()), string(cookie.Value()))
}
```
Click here to see the result
-```
+```text
go => fiber
```
@@ -149,22 +149,22 @@ func (r *Response) JSON(v any) error
```go title="Example"
type Body struct {
- Slideshow struct {
- Author string `json:"author"`
- Date string `json:"date"`
- Title string `json:"title"`
- } `json:"slideshow"`
+ Slideshow struct {
+ Author string `json:"author"`
+ Date string `json:"date"`
+ Title string `json:"title"`
+ } `json:"slideshow"`
}
var out Body
resp, err := client.Get("https://httpbin.org/json")
if err != nil {
- panic(err)
+ panic(err)
}
err = resp.JSON(&out)
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Printf("%+v\n", out)
@@ -173,7 +173,7 @@ fmt.Printf("%+v\n", out)
Click here to see the result
-```
+```text
{Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}}
```
diff --git a/docs/client/rest.md b/docs/client/rest.md
index 9b34b80f..3590407e 100644
--- a/docs/client/rest.md
+++ b/docs/client/rest.md
@@ -10,36 +10,38 @@ toc_max_heading_level: 5
The Fiber Client for Fiber v3 is a powerful HTTP client optimized for high performance and ease of use in server-side applications. Built on top of the robust FastHTTP library, it inherits FastHTTP's high-speed HTTP protocol implementation. The client is designed to make HTTP requests both internally within services or externally to other web services.
## Features
+
- **Lightweight & Fast**: Leveraging the minimalistic design of FastHTTP, the Fiber Client is lightweight and extremely fast.
- **Flexible Configuration**: Configure client-level settings such as timeouts, headers, and more, which apply to all requests. Specific requests can further override or merge these settings.
- **Connection Pooling**: Manages a pool of persistent connections that reduce the overhead of repeatedly establishing connections.
- **Timeouts & Retries**: Supports setting request timeouts and retry mechanisms to handle transient failures.
## Usage
+
To use the Fiber Client, instantiate it with the desired configuration. Here's a simple example:
```go
package main
import (
- "fmt"
- "time"
+ "fmt"
+ "time"
- "github.com/gofiber/fiber/v3/client"
+ "github.com/gofiber/fiber/v3/client"
)
func main() {
- cc := client.New()
- cc.SetTimeout(10 * time.Second)
+ cc := client.New()
+ cc.SetTimeout(10 * time.Second)
- // Get request
- resp, err := cc.Get("https://httpbin.org/get")
- if err != nil {
- panic(err)
- }
+ // Get request
+ resp, err := cc.Get("https://httpbin.org/get")
+ if err != nil {
+ panic(err)
+ }
- fmt.Printf("Status: %d\n", resp.StatusCode())
- fmt.Printf("Body: %s\n", string(resp.Body()))
+ fmt.Printf("Status: %d\n", resp.StatusCode())
+ fmt.Printf("Body: %s\n", string(resp.Body()))
}
```
@@ -47,49 +49,49 @@ You can check out [examples](examples.md) for more examples!
```go
type Client struct {
- mu sync.RWMutex
+ mu sync.RWMutex
- fasthttp *fasthttp.Client
+ fasthttp *fasthttp.Client
- baseURL string
- userAgent string
- referer string
- header *Header
- params *QueryParam
- cookies *Cookie
- path *PathParam
+ baseURL string
+ userAgent string
+ referer string
+ header *Header
+ params *QueryParam
+ cookies *Cookie
+ path *PathParam
- debug bool
+ debug bool
- timeout time.Duration
+ timeout time.Duration
- // user defined request hooks
- userRequestHooks []RequestHook
+ // user defined request hooks
+ userRequestHooks []RequestHook
- // client package defined request hooks
- builtinRequestHooks []RequestHook
+ // client package defined request hooks
+ builtinRequestHooks []RequestHook
- // user defined response hooks
- userResponseHooks []ResponseHook
+ // user defined response hooks
+ userResponseHooks []ResponseHook
- // client package defined response hooks
- builtinResponseHooks []ResponseHook
+ // client package defined response hooks
+ builtinResponseHooks []ResponseHook
- jsonMarshal utils.JSONMarshal
- jsonUnmarshal utils.JSONUnmarshal
- xmlMarshal utils.XMLMarshal
- xmlUnmarshal utils.XMLUnmarshal
+ jsonMarshal utils.JSONMarshal
+ jsonUnmarshal utils.JSONUnmarshal
+ xmlMarshal utils.XMLMarshal
+ xmlUnmarshal utils.XMLUnmarshal
- cookieJar *CookieJar
+ cookieJar *CookieJar
- // proxy
- proxyURL string
+ // proxy
+ proxyURL string
- // retry
- retryConfig *RetryConfig
+ // retry
+ retryConfig *RetryConfig
- // logger
- logger log.CommonLogger
+ // logger
+ logger log.CommonLogger
}
```
@@ -175,21 +177,21 @@ It can be used to configure request data while sending requests using Get, Post,
```go
type Config struct {
- Ctx context.Context
+ Ctx context.Context
- UserAgent string
- Referer string
- Header map[string]string
- Param map[string]string
- Cookie map[string]string
- PathParam map[string]string
+ UserAgent string
+ Referer string
+ Header map[string]string
+ Param map[string]string
+ Cookie map[string]string
+ PathParam map[string]string
- Timeout time.Duration
- MaxRedirects int
+ Timeout time.Duration
+ MaxRedirects int
- Body any
- FormData map[string]string
- File []*File
+ Body any
+ FormData map[string]string
+ File []*File
}
```
@@ -393,7 +395,7 @@ cc.SetBaseURL("https://httpbin.org/")
resp, err := cc.Get("/get")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -402,12 +404,13 @@ fmt.Println(string(resp.Body()))
Click here to see the result
-```
+```json
{
"args": {},
...
}
```
+
### Header
@@ -441,7 +444,7 @@ func (c *Client) SetHeader(key, val string) *Client
#### AddHeaders
AddHeaders method adds multiple headers field and its values at one go in the client instance.
-These headers will be applied to all requests raised from this client instance.
+These headers will be applied to all requests raised from this client instance.
Also it can be overridden at request level headers options.
```go title="Signature"
@@ -451,7 +454,7 @@ func (c *Client) AddHeaders(h map[string][]string) *Client
#### SetHeaders
SetHeaders method sets multiple headers field and its values at one go in the client instance.
-These headers will be applied to all requests raised from this client instance.
+These headers will be applied to all requests raised from this client instance.
Also it can be overridden at request level headers options.
```go title="Signature"
@@ -489,7 +492,7 @@ func (c *Client) SetParam(key, val string) *Client
#### AddParams
AddParams method adds multiple query params field and its values at one go in the client instance.
-These params will be applied to all requests raised from this client instance.
+These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature"
@@ -499,7 +502,7 @@ func (c *Client) AddParams(m map[string][]string) *Client
#### SetParams
SetParams method sets multiple params field and its values at one go in the client instance.
-These params will be applied to all requests raised from this client instance.
+These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature"
@@ -509,7 +512,7 @@ func (c *Client) SetParams(m map[string]string) *Client
#### SetParamsWithStruct
SetParamsWithStruct method sets multiple params field and its values at one go in the client instance.
-These params will be applied to all requests raised from this client instance.
+These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature"
@@ -566,7 +569,7 @@ func (c *Client) SetPathParam(key, val string) *Client
#### SetPathParams
SetPathParams method sets multiple path params field and its values at one go in the client instance.
-These path params will be applied to all requests raised from this client instance.
+These path params will be applied to all requests raised from this client instance.
Also it can be overridden at request level path params options.
```go title="Signature"
@@ -576,7 +579,7 @@ func (c *Client) SetPathParams(m map[string]string) *Client
#### SetPathParamsWithStruct
SetPathParamsWithStruct method sets multiple path params field and its values at one go in the client instance.
-These path params will be applied to all requests raised from this client instance.
+These path params will be applied to all requests raised from this client instance.
Also it can be overridden at request level path params options.
```go title="Signature"
@@ -616,7 +619,7 @@ cc.SetCookie("john", "doe")
resp, err := cc.Get("https://httpbin.org/cookies")
if err != nil {
- panic(err)
+ panic(err)
}
fmt.Println(string(resp.Body()))
@@ -625,19 +628,20 @@ fmt.Println(string(resp.Body()))
Click here to see the result
-```
+```json
{
"cookies": {
"john": "doe"
}
}
```
+
#### SetCookies
SetCookies method sets multiple cookies field and its values at one go in the client instance.
-These cookies will be applied to all requests raised from this client instance.
+These cookies will be applied to all requests raised from this client instance.
Also it can be overridden at request level cookie options.
```go title="Signature"
diff --git a/docs/extra/faq.md b/docs/extra/faq.md
index eaea2a2c..3e48c89e 100644
--- a/docs/extra/faq.md
+++ b/docs/extra/faq.md
@@ -20,7 +20,7 @@ Routes and other application-specific logic can live in as many files as you wis
## How do I handle custom 404 responses?
-If you're using v2.32.0 or later, all you need to do is to implement a custom error handler. See below, or see a more detailed explanation at [Error Handling](../guide/error-handling.md#custom-error-handler).
+If you're using v2.32.0 or later, all you need to do is to implement a custom error handler. See below, or see a more detailed explanation at [Error Handling](../guide/error-handling.md#custom-error-handler).
If you're using v2.31.0 or earlier, the error handler will not capture 404 errors. Instead, you need to add a middleware function at the very bottom of the stack \(below all other functions\) to handle a 404 response:
@@ -32,12 +32,13 @@ app.Use(func(c fiber.Ctx) error {
## How can i use live reload ?
-[Air](https://github.com/cosmtrek/air) is a handy tool that automatically restarts your Go applications whenever the source code changes, making your development process faster and more efficient.
+[Air](https://github.com/air-verse/air) is a handy tool that automatically restarts your Go applications whenever the source code changes, making your development process faster and more efficient.
To use Air in a Fiber project, follow these steps:
-1. Install Air by downloading the appropriate binary for your operating system from the GitHub release page or by building the tool directly from source.
-2. Create a configuration file for Air in your project directory. This file can be named, for example, .air.toml or air.conf. Here's a sample configuration file that works with Fiber:
+* Install Air by downloading the appropriate binary for your operating system from the GitHub release page or by building the tool directly from source.
+* Create a configuration file for Air in your project directory. This file can be named, for example, .air.toml or air.conf. Here's a sample configuration file that works with Fiber:
+
```toml
# .air.toml
root = "."
@@ -50,7 +51,9 @@ tmp_dir = "tmp"
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_regex = ["_test\\.go"]
```
-3. Start your Fiber application using Air by running the following command in the terminal:
+
+* Start your Fiber application using Air by running the following command in the terminal:
+
```sh
air
```
@@ -59,7 +62,6 @@ As you make changes to your source code, Air will detect them and automatically
A complete example demonstrating the use of Air with Fiber can be found in the [Fiber Recipes repository](https://github.com/gofiber/recipes/tree/master/air). This example shows how to configure and use Air in a Fiber project to create an efficient development environment.
-
## How do I set up an error handler?
To override the default error handler, you can override the default when providing a [Config](../api/fiber.md#errorhandler) when initiating a new [Fiber instance](../api/fiber.md#new).
@@ -92,78 +94,80 @@ To learn more about using Templates in Fiber, see [Templates](../guide/templates
## Does Fiber have a community chat?
-Yes, we have our own [Discord ](https://gofiber.io/discord)server, where we hang out. We have different rooms for every subject.
+Yes, we have our own [Discord](https://gofiber.io/discord)server, where we hang out. We have different rooms for every subject.
If you have questions or just want to have a chat, feel free to join us via this **>** [**invite link**](https://gofiber.io/discord) **<**.

## Does fiber support sub domain routing ?
-Yes we do, here are some examples:
+Yes we do, here are some examples:
This example works v2
+
```go
package main
import (
- "log"
+ "log"
- "github.com/gofiber/fiber/v3"
- "github.com/gofiber/fiber/v3/middleware/logger"
+ "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3/middleware/logger"
)
type Host struct {
- Fiber *fiber.App
+ Fiber *fiber.App
}
func main() {
- // Hosts
- hosts := map[string]*Host{}
- //-----
- // API
- //-----
- api := fiber.New()
- api.Use(logger.New(logger.Config{
- Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
- }))
- hosts["api.localhost:3000"] = &Host{api}
- api.Get("/", func(c fiber.Ctx) error {
- return c.SendString("API")
- })
- //------
- // Blog
- //------
- blog := fiber.New()
- blog.Use(logger.New(logger.Config{
- Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
- }))
- hosts["blog.localhost:3000"] = &Host{blog}
- blog.Get("/", func(c fiber.Ctx) error {
- return c.SendString("Blog")
- })
- //---------
- // Website
- //---------
- site := fiber.New()
- site.Use(logger.New(logger.Config{
- Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
- }))
+ // Hosts
+ hosts := map[string]*Host{}
+ //-----
+ // API
+ //-----
+ api := fiber.New()
+ api.Use(logger.New(logger.Config{
+ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
+ }))
+ hosts["api.localhost:3000"] = &Host{api}
+ api.Get("/", func(c fiber.Ctx) error {
+ return c.SendString("API")
+ })
+ //------
+ // Blog
+ //------
+ blog := fiber.New()
+ blog.Use(logger.New(logger.Config{
+ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
+ }))
+ hosts["blog.localhost:3000"] = &Host{blog}
+ blog.Get("/", func(c fiber.Ctx) error {
+ return c.SendString("Blog")
+ })
+ //---------
+ // Website
+ //---------
+ site := fiber.New()
+ site.Use(logger.New(logger.Config{
+ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
+ }))
- hosts["localhost:3000"] = &Host{site}
- site.Get("/", func(c fiber.Ctx) error {
- return c.SendString("Website")
- })
- // Server
- app := fiber.New()
- app.Use(func(c fiber.Ctx) error {
- host := hosts[c.Hostname()]
- if host == nil {
- return c.SendStatus(fiber.StatusNotFound)
- } else {
- host.Fiber.Handler()(c.Context())
- return nil
- }
- })
- log.Fatal(app.Listen(":3000"))
+ hosts["localhost:3000"] = &Host{site}
+ site.Get("/", func(c fiber.Ctx) error {
+ return c.SendString("Website")
+ })
+ // Server
+ app := fiber.New()
+ app.Use(func(c fiber.Ctx) error {
+ host := hosts[c.Hostname()]
+ if host == nil {
+ return c.SendStatus(fiber.StatusNotFound)
+ } else {
+ host.Fiber.Handler()(c.Context())
+ return nil
+ }
+ })
+ log.Fatal(app.Listen(":3000"))
}
```
+
If more information is needed, please refer to this issue [#750](https://github.com/gofiber/fiber/issues/750)
diff --git a/docs/guide/error-handling.md b/docs/guide/error-handling.md
index 584f28ed..17fe4d75 100644
--- a/docs/guide/error-handling.md
+++ b/docs/guide/error-handling.md
@@ -24,6 +24,7 @@ app.Get("/", func(c fiber.Ctx) error {
return c.SendFile("file-does-not-exist")
})
```
+
diff --git a/docs/guide/faster-fiber.md b/docs/guide/faster-fiber.md
index d4c2df1b..90d2a358 100644
--- a/docs/guide/faster-fiber.md
+++ b/docs/guide/faster-fiber.md
@@ -5,13 +5,13 @@ sidebar_position: 7
---
## Custom JSON Encoder/Decoder
-Since Fiber v2.32.0, we use **encoding/json** as default json library due to stability and producibility. However, the standard library is a bit slow compared to 3rd party libraries. If you're not happy with the performance of **encoding/json**, we recommend you to use these libraries:
+
+Since Fiber v2.32.0, we have adopted `encoding/json` as the default JSON library for its stability and reliability. However, the standard library can be slower than some third-party alternatives. If you find the performance of `encoding/json` unsatisfactory, we suggest considering these libraries:
+
- [goccy/go-json](https://github.com/goccy/go-json)
- [bytedance/sonic](https://github.com/bytedance/sonic)
- [segmentio/encoding](https://github.com/segmentio/encoding)
-- [mailru/easyjson](https://github.com/mailru/easyjson)
- [minio/simdjson-go](https://github.com/minio/simdjson-go)
-- [wI2L/jettison](https://github.com/wI2L/jettison)
```go title="Example"
package main
@@ -20,12 +20,12 @@ import "github.com/gofiber/fiber/v3"
import "github.com/goccy/go-json"
func main() {
- app := fiber.New(fiber.Config{
- JSONEncoder: json.Marshal,
- JSONDecoder: json.Unmarshal,
- })
+ app := fiber.New(fiber.Config{
+ JSONEncoder: json.Marshal,
+ JSONDecoder: json.Unmarshal,
+ })
- # ...
+ # ...
}
```
diff --git a/docs/guide/grouping.md b/docs/guide/grouping.md
index 4ed4622c..6374f102 100644
--- a/docs/guide/grouping.md
+++ b/docs/guide/grouping.md
@@ -10,23 +10,23 @@ In general, the Group functionality in Fiber behaves similarly to ExpressJS. Gro
## Paths
-Like **Routing**, groups can also have paths that belong to a cluster.
+Like `Routing`, groups can also have paths that belong to a cluster.
```go
func main() {
- app := fiber.New()
+ app := fiber.New()
- api := app.Group("/api", middleware) // /api
+ api := app.Group("/api", middleware) // /api
- v1 := api.Group("/v1", middleware) // /api/v1
- v1.Get("/list", handler) // /api/v1/list
- v1.Get("/user", handler) // /api/v1/user
+ v1 := api.Group("/v1", middleware) // /api/v1
+ v1.Get("/list", handler) // /api/v1/list
+ v1.Get("/user", handler) // /api/v1/user
- v2 := api.Group("/v2", middleware) // /api/v2
- v2.Get("/list", handler) // /api/v2/list
- v2.Get("/user", handler) // /api/v2/user
+ v2 := api.Group("/v2", middleware) // /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"))
}
```
@@ -34,19 +34,19 @@ A **Group** of paths can have an optional handler.
```go
func main() {
- app := fiber.New()
+ app := fiber.New()
- api := app.Group("/api") // /api
+ api := app.Group("/api") // /api
- v1 := api.Group("/v1") // /api/v1
- v1.Get("/list", handler) // /api/v1/list
- v1.Get("/user", handler) // /api/v1/user
+ v1 := api.Group("/v1") // /api/v1
+ v1.Get("/list", handler) // /api/v1/list
+ v1.Get("/user", handler) // /api/v1/user
- v2 := api.Group("/v2") // /api/v2
- v2.Get("/list", handler) // /api/v2/list
- v2.Get("/user", handler) // /api/v2/user
+ v2 := api.Group("/v2") // /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"))
}
```
diff --git a/docs/guide/routing.md b/docs/guide/routing.md
index 7dcc2b61..450932cc 100644
--- a/docs/guide/routing.md
+++ b/docs/guide/routing.md
@@ -20,12 +20,12 @@ import RoutingHandler from './../partials/routing/handler.md';
Route paths, combined with a request method, define the endpoints at which requests can be made. Route paths can be **strings** or **string patterns**.
-**Examples of route paths based on strings**
+### Examples of route paths based on strings
```go
// This route path will match requests to the root route, "/":
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString("root")
+ return c.SendString("root")
})
// This route path will match requests to "/about":
@@ -56,7 +56,7 @@ Greedy parameters are indicated by wildcard\(\*\) or plus\(+\) signs.
The routing also offers the possibility to use optional parameters, for the named parameters these are marked with a final "?", unlike the plus sign which is not optional, you can use the wildcard character for a parameter range which is optional and greedy.
-**Example of define routes with route parameters**
+### Example of define routes with route parameters
```go
// Parameters
@@ -143,6 +143,7 @@ app.Get("/v1/*/shop/*", handler)
We have adapted the routing strongly to the express routing, but currently without the possibility of the regular expressions, because they are quite slow. The possibilities can be tested with version 0.1.7 \(express 4\) in the online [Express route tester](http://forbeslindesay.github.io/express-route-tester/).
### Constraints
+
Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values by parameters. The feature was introduced in `v2.37.0` and inspired by [.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#route-constraints).
:::caution
@@ -172,7 +173,7 @@ Constraints aren't validation for parameters. If constraints aren't valid for a
```go
app.Get("/:test", func(c fiber.Ctx) error {
- return c.SendString(c.Params("test"))
+ return c.SendString(c.Params("test"))
})
// curl -X GET http://localhost:3000/12
@@ -181,13 +182,15 @@ app.Get("/:test", func(c fiber.Ctx) error {
// curl -X GET http://localhost:3000/1
// Cannot GET /1
```
+
You can use `;` for multiple constraints.
+
```go
app.Get("/:test", func(c fiber.Ctx) error {
- return c.SendString(c.Params("test"))
+ return c.SendString(c.Params("test"))
})
// curl -X GET http://localhost:3000/120000
@@ -199,13 +202,15 @@ app.Get("/:test", func(c fiber.Ctx) error {
// curl -X GET http://localhost:3000/250
// 250
```
+
Fiber precompiles regex query when to register routes. So there're no performance overhead for regex constraint.
+
```go
app.Get(`/:date`, func(c fiber.Ctx) error {
- return c.SendString(c.Params("date"))
+ return c.SendString(c.Params("date"))
})
// curl -X GET http://localhost:3000/125
@@ -245,21 +250,21 @@ app.Get("/:test?", func(c fiber.Ctx) error {
Custom constraints can be added to Fiber using the `app.RegisterCustomConstraint` method. Your constraints have to be compatible with the `CustomConstraint` interface.
-It is a good idea to add external constraints to your project once you want to add more specific rules to your routes.
+It is a good idea to add external constraints to your project once you want to add more specific rules to your routes.
For example, you can add a constraint to check if a parameter is a valid ULID.
```go
// CustomConstraint is an interface for custom constraints
type CustomConstraint interface {
- // Name returns the name of the constraint.
- // This name is used in the constraint matching.
- Name() string
+ // Name returns the name of the constraint.
+ // This name is used in the constraint matching.
+ Name() string
- // Execute executes the constraint.
- // It returns true if the constraint is matched and right.
- // param is the parameter value to check.
- // args are the constraint arguments.
- Execute(param string, args ...string) bool
+ // Execute executes the constraint.
+ // It returns true if the constraint is matched and right.
+ // param is the parameter value to check.
+ // args are the constraint arguments.
+ Execute(param string, args ...string) bool
}
```
@@ -267,30 +272,30 @@ You can check the example below:
```go
type UlidConstraint struct {
- fiber.CustomConstraint
+ fiber.CustomConstraint
}
func (*UlidConstraint) Name() string {
- return "ulid"
+ return "ulid"
}
func (*UlidConstraint) Execute(param string, args ...string) bool {
- _, err := ulid.Parse(param)
- return err == nil
+ _, err := ulid.Parse(param)
+ return err == nil
}
func main() {
- app := fiber.New()
- app.RegisterCustomConstraint(&UlidConstraint{})
+ app := fiber.New()
+ app.RegisterCustomConstraint(&UlidConstraint{})
- app.Get("/login/:id", func(c fiber.Ctx) error {
- return c.SendString("...")
- })
+ app.Get("/login/:id", func(c fiber.Ctx) error {
+ return c.SendString("...")
+ })
- app.Listen(":3000")
+ app.Listen(":3000")
- // /login/01HK7H9ZE5BFMK348CPYP14S0Z -> 200
- // /login/12345 -> 404
+ // /login/01HK7H9ZE5BFMK348CPYP14S0Z -> 200
+ // /login/12345 -> 404
}
```
@@ -302,15 +307,15 @@ Functions that are designed to make changes to the request or response are calle
```go
app.Use(func(c fiber.Ctx) error {
- // Set a custom header on all responses:
- c.Set("X-Custom-Header", "Hello, World")
+ // Set a custom header on all responses:
+ c.Set("X-Custom-Header", "Hello, World")
- // Go to next middleware:
- return c.Next()
+ // Go to next middleware:
+ return c.Next()
})
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString("Hello, World!")
+ return c.SendString("Hello, World!")
})
```
@@ -322,26 +327,25 @@ app.Get("/", func(c fiber.Ctx) error {
Adding routes dynamically after the application has started is not supported due to design and performance considerations. Make sure to define all your routes before the application starts.
:::
-
## Grouping
If you have many endpoints, you can organize your routes using `Group`.
```go
func main() {
- app := fiber.New()
+ app := fiber.New()
- api := app.Group("/api", middleware) // /api
+ api := app.Group("/api", middleware) // /api
- v1 := api.Group("/v1", middleware) // /api/v1
- v1.Get("/list", handler) // /api/v1/list
- v1.Get("/user", handler) // /api/v1/user
+ v1 := api.Group("/v1", middleware) // /api/v1
+ v1.Get("/list", handler) // /api/v1/list
+ v1.Get("/user", handler) // /api/v1/user
- v2 := api.Group("/v2", middleware) // /api/v2
- v2.Get("/list", handler) // /api/v2/list
- v2.Get("/user", handler) // /api/v2/user
+ v2 := api.Group("/v2", middleware) // /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"))
}
```
diff --git a/docs/guide/templates.md b/docs/guide/templates.md
index 2bd441fe..af2ea4e4 100644
--- a/docs/guide/templates.md
+++ b/docs/guide/templates.md
@@ -8,47 +8,29 @@ sidebar_position: 3
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-## Template interfaces
+Templates are a great tool to render dynamic content without using a separate frontend framework.
-Fiber provides a Views interface to provide your own template engine:
+## Template Engines
-
-
+Fiber allows you to provide a custom template engine at app initialization.
```go
-type Views interface {
- Load() error
- Render(out io.Writer, name string, binding any, layout ...string) error
-}
-```
-
-
-
-`Views` interface contains a `Load` and `Render` method, `Load` is executed by Fiber on app initialization to load/parse the templates.
-
-```go
-// Pass engine to Fiber's Views Engine
app := fiber.New(fiber.Config{
+ // Pass in Views Template Engine
Views: engine,
- // Views Layout is the global layout for all template render until override on Render function.
- ViewsLayout: "layouts/main"
+
+ // Default global path to search for views (can be overriden when calling Render())
+ ViewsLayout: "layouts/main",
+
+ // Enables/Disables access to `ctx.Locals()` entries in rendered views
+ // (defaults to false)
+ PassLocalsToViews: false,
})
```
-The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data. It will use global layout if layout is not being defined in `Render` function.
-If the Fiber config option `PassLocalsToViews` is enabled, then all locals set using `ctx.Locals(key, value)` will be passed to the template.
+### Supported Engines
-```go
-app.Get("/", func(c fiber.Ctx) error {
- return c.Render("index", fiber.Map{
- "hello": "world",
- });
-})
-```
-
-## Engines
-
-Fiber team maintains [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
+The Fiber team maintains a [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
* [ace](https://docs.gofiber.io/template/ace/)
* [amber](https://docs.gofiber.io/template/amber/)
@@ -60,6 +42,174 @@ Fiber team maintains [templates](https://docs.gofiber.io/template) package that
* [pug](https://docs.gofiber.io/template/pug)
* [slim](https://docs.gofiber.io/template/slim)
+:::info
+Custom template engines can implement the `Views` interface to be supported in Fiber.
+:::
+
+```go title="Views interface"
+type Views interface {
+ // Fiber executes Load() on app initialization to load/parse the templates
+ Load() error
+
+ // Outputs a template to the provided buffer using the provided template,
+ // template name, and binded data
+ Render(io.Writer, string, interface{}, ...string) error
+}
+```
+
+:::note
+The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data.
+:::
+
+## Rendering Templates
+
+Once an engine is set up, a route handler can call the [**ctx.Render\(\)**](../api/ctx.md#render) function with a template name and binded data to send the rendered template.
+
+```go title="Signature"
+func (c Ctx) Render(name string, bind Map, layouts ...string) error
+```
+
+:::info
+By default, [**ctx.Render\(\)**](../api/ctx.md#render) searches for the template name in the `ViewsLayout` path. To override this setting, provide the path(s) in the `layouts` argument.
+:::
+
+
+
+
+```go
+app.Get("/", func(c fiber.Ctx) error {
+ return c.Render("index", fiber.Map{
+ "Title": "Hello, World!",
+ })
+
+})
+```
+
+
+
+
+
+```html
+
+
+
+
{{.Title}}
+
+
+```
+
+
+
+
+
+:::caution
+If the Fiber config option `PassLocalsToViews` is enabled, then all locals set using `ctx.Locals(key, value)` will be passed to the template. It is important to avoid clashing keys when using this setting.
+:::
+
+## Advanced Templating
+
+### Custom Functions
+
+Fiber supports adding custom functions to templates.
+
+#### AddFunc
+
+Adds a global function to all templates.
+
+```go title="Signature"
+func (e *Engine) AddFunc(name string, fn interface{}) IEngineCore
+```
+
+
+
+
+```go
+// Add `ToUpper` to engine
+engine := html.New("./views", ".html")
+engine.AddFunc("ToUpper", func(s string) string {
+ return strings.ToUpper(s)
+}
+
+// Initialize Fiber App
+app := fiber.New(fiber.Config{
+ Views: engine,
+})
+
+app.Get("/", func (c fiber.Ctx) error {
+ return c.Render("index", fiber.Map{
+ "Content": "hello, World!"
+ })
+})
+```
+
+
+
+
+```html
+
+
+
+