├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.yaml │ ├── feature-request.yaml │ └── question.yaml ├── dependabot.yml ├── labeler.yml ├── logo-dark.svg ├── logo.svg ├── release-drafter-template.yml ├── release.yml ├── scripts │ ├── parallel-go-test.sh │ └── sync_docs.sh └── workflows │ ├── auto-labeler.yml │ ├── dependabot_automerge.yml │ ├── golangci-lint.yml │ ├── release-drafter.yml │ ├── sync-docs.yml │ ├── test-casbin.yml │ ├── test-circuitbreaker.yml │ ├── test-fgprof.yml │ ├── test-fiberi18n.yml │ ├── test-fibernewrelic.yml │ ├── test-fibersentry.yml │ ├── test-fiberzap.yml │ ├── test-fiberzerolog.yml │ ├── test-hcaptcha.yml │ ├── test-jwt.yml │ ├── test-loadshed.yml │ ├── test-monitor.yml │ ├── test-opafiber.yml │ ├── test-otelfiber.yml │ ├── test-paseto.yml │ ├── test-socketio.yml │ ├── test-swagger.yml │ ├── test-testcontainers.yml │ └── test-websocket.yml ├── .gitignore ├── LICENSE ├── README.md ├── casbin ├── README.md ├── casbin.go ├── casbin_test.go ├── config.go ├── go.mod ├── go.sum ├── options.go └── utils.go ├── circuitbreaker ├── README.md ├── circuitbreaker.go ├── circuitbreaker_test.go ├── go.mod └── go.sum ├── fgprof ├── README.md ├── config.go ├── fgprof.go ├── fgprof_test.go ├── go.mod └── go.sum ├── fiberi18n ├── README.md ├── config.go ├── embed.go ├── embed_test.go ├── example │ ├── localize │ │ ├── en.yaml │ │ └── zh.yaml │ ├── localizeJSON │ │ ├── en.json │ │ └── zh.json │ └── main.go ├── go.mod ├── go.sum ├── i18n.go └── i18n_test.go ├── fibernewrelic ├── README.md ├── fiber.go ├── fiber_test.go ├── go.mod └── go.sum ├── fibersentry ├── README.md ├── config.go ├── go.mod ├── go.sum ├── sentry.go └── sentry_test.go ├── fiberzap ├── README.md ├── config.go ├── go.mod ├── go.sum ├── logger.go ├── logger_test.go ├── zap.go └── zap_test.go ├── fiberzerolog ├── README.md ├── config.go ├── go.mod ├── go.sum ├── zerolog.go └── zerolog_test.go ├── go.work ├── hcaptcha ├── README.md ├── config.go ├── go.mod ├── go.sum ├── hcaptcha.go └── hcaptcha_test.go ├── jwt ├── README.md ├── config.go ├── config_test.go ├── crypto.go ├── go.mod ├── go.sum ├── jwt.go └── jwt_test.go ├── loadshed ├── README.md ├── cpu.go ├── go.mod ├── go.sum ├── loadshed.go └── loadshed_test.go ├── monitor ├── README.md ├── config.go ├── config_test.go ├── go.mod ├── go.sum ├── index.go ├── monitor.go └── monitor_test.go ├── opafiber ├── README.md ├── fiber.go ├── fiber_test.go ├── go.mod └── go.sum ├── otelfiber ├── README.md ├── config.go ├── doc.go ├── example │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── go.mod │ ├── go.sum │ └── server.go ├── fiber.go ├── go.mod ├── go.sum ├── internal │ ├── http.go │ └── http_test.go ├── otelfiber_test │ └── fiber_test.go └── semconv.go ├── paseto ├── README.md ├── config.go ├── config_test.go ├── go.mod ├── go.sum ├── helpers.go ├── paseto.go ├── paseto_test.go └── payload.go ├── socketio ├── README.md ├── go.mod ├── go.sum ├── socketio.go └── socketio_test.go ├── swagger ├── README.md ├── go.mod ├── go.sum ├── swagger.go ├── swagger.json ├── swagger.yaml ├── swagger_missing.json └── swagger_test.go ├── testcontainers ├── README.md ├── config.go ├── examples_test.go ├── go.mod ├── go.sum ├── testcontainers.go ├── testcontainers_test.go └── testcontainers_unit_test.go └── websocket ├── README.md ├── go.mod ├── go.sum ├── websocket.go └── websocket_test.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gofiber/maintainers -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | title: "\U0001F41B [Bug]: " 3 | description: Create a bug report to help us fix it. 4 | labels: ["☢️ Bug"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think Fiber contrib don't have a nice feature that you think, open the issue with **✏️ Feature Request** template. 14 | - Write your issue with clear and understandable English. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: "Bug Description" 19 | description: "A clear and detailed description of what the bug is." 20 | placeholder: "Explain your problem as clear and detailed." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: how-to-reproduce 25 | attributes: 26 | label: How to Reproduce 27 | description: "Steps to reproduce the behavior and what should be observed in the end." 28 | placeholder: "Tell us step by step how we can replicate your problem and what we should see in the end." 29 | value: | 30 | Steps to reproduce the behavior: 31 | 1. Go to '....' 32 | 2. Click on '....' 33 | 3. Do '....' 34 | 4. See '....' 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: expected-behavior 39 | attributes: 40 | label: Expected Behavior 41 | description: "A clear and detailed description of what you think should happens." 42 | placeholder: "Tell us what contrib should normally do." 43 | validations: 44 | required: true 45 | - type: input 46 | id: version 47 | attributes: 48 | label: "Contrib package Version" 49 | description: "Some bugs may be fixed in future contrib releases, so we have to know your contrib package version." 50 | placeholder: "Write your contrib version. (v1.0.0, v1.1.0...)" 51 | validations: 52 | required: true 53 | - type: textarea 54 | id: snippet 55 | attributes: 56 | label: "Code Snippet (optional)" 57 | description: "For some issues, we need to know some parts of your code." 58 | placeholder: "Share a code you think related to the issue." 59 | render: go 60 | value: | 61 | package main 62 | 63 | import "github.com/gofiber/contrib/%package%" 64 | 65 | func main() { 66 | // Steps to reproduce 67 | } 68 | - type: checkboxes 69 | id: terms 70 | attributes: 71 | label: "Checklist:" 72 | description: "By submitting this issue, you confirm that:" 73 | options: 74 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 75 | required: true 76 | - label: "I have checked for existing issues that describe my problem prior to opening this one." 77 | required: true 78 | - label: "I understand that improperly formatted bug reports may be closed without explanation." 79 | required: true 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | title: "\U0001F680 [Feature]: " 3 | description: Suggest an idea to improve this project. 4 | labels: ["✏️ Feature"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think this is just a bug, open the issue with **☢️ Bug Report** template. 14 | - Write your issue with clear and understandable English. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: "Feature Description" 19 | description: "A clear and detailed description of the feature we need to do." 20 | placeholder: "Explain your feature as clear and detailed." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: additional-context 25 | attributes: 26 | label: "Additional Context (optional)" 27 | description: "If you have something else to describe, write them here." 28 | placeholder: "Write here what you can describe differently." 29 | - type: textarea 30 | id: snippet 31 | attributes: 32 | label: "Code Snippet (optional)" 33 | description: "Code snippet may be really helpful to describe some features." 34 | placeholder: "Share a code to explain the feature better." 35 | render: go 36 | value: | 37 | package main 38 | 39 | import "github.com/gofiber/contrib/%package%" 40 | 41 | func main() { 42 | // Steps to reproduce 43 | } 44 | - type: checkboxes 45 | id: terms 46 | attributes: 47 | label: "Checklist:" 48 | description: "By submitting this issue, you confirm that:" 49 | options: 50 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 51 | required: true 52 | - label: "I have checked for existing issues that describe my suggestion prior to opening this one." 53 | required: true 54 | - label: "I understand that improperly formatted feature requests may be closed without explanation." 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yaml: -------------------------------------------------------------------------------- 1 | name: "🤔 Question" 2 | title: "\U0001F917 [Question]: " 3 | description: Ask a question so we can help you easily. 4 | labels: ["🤔 Question"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think this is just a bug, open the issue with **☢️ Bug Report** template. 14 | - If you think Fiber contrib don't have a nice feature that you think, open the issue with **✏️ Feature Request** template. 15 | - Write your issue with clear and understandable English. 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: "Question Description" 20 | description: "A clear and detailed description of the question." 21 | placeholder: "Explain your question as clear and detailed." 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: snippet 26 | attributes: 27 | label: "Code Snippet (optional)" 28 | description: "Code snippet may be really helpful to describe some features." 29 | placeholder: "Share a code to explain the feature better." 30 | render: go 31 | value: | 32 | package main 33 | 34 | import "github.com/gofiber/contrib/%package%" 35 | 36 | func main() { 37 | // Steps to reproduce 38 | } 39 | - type: checkboxes 40 | id: terms 41 | attributes: 42 | label: "Checklist:" 43 | description: "By submitting this issue, you confirm that:" 44 | options: 45 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 46 | required: true 47 | - label: "I have checked for existing issues that describe my questions prior to opening this one." 48 | required: true 49 | - label: "I understand that improperly formatted questions may be closed without explanation." 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#directories 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | open-pull-requests-limit: 30 8 | directory: "/" 9 | labels: 10 | - "🤖 Dependencies" 11 | schedule: 12 | interval: "daily" 13 | - package-ecosystem: "gomod" 14 | open-pull-requests-limit: 30 15 | directories: 16 | - "**/*" 17 | labels: 18 | - "🤖 Dependencies" 19 | schedule: 20 | interval: "daily" 21 | groups: 22 | fiber-modules: 23 | patterns: 24 | - "github.com/gofiber/fiber/**" 25 | fasthttp-modules: 26 | patterns: 27 | - "github.com/valyala/fasthttp" 28 | - "github.com/valyala/fasthttp/**" 29 | golang-modules: 30 | patterns: 31 | - "golang.org/x/**" 32 | opentelemetry-modules: 33 | patterns: 34 | - "go.opentelemetry.io/**" 35 | fasthttp-websocket-modules: 36 | patterns: 37 | - "github.com/fasthttp/websocket/**" 38 | valyala-utils-modules: 39 | patterns: 40 | - "github.com/valyala/bytebufferpool" 41 | - "github.com/valyala/tcplisten" 42 | mattn-modules: 43 | patterns: 44 | - "github.com/mattn/go-colorable" 45 | - "github.com/mattn/go-isatty" 46 | - "github.com/mattn/go-runewidth" 47 | google-modules: 48 | patterns: 49 | - "github.com/google/**" 50 | - "google.golang.org/**" 51 | testing-modules: 52 | patterns: 53 | - "github.com/stretchr/**" 54 | - "github.com/davecgh/go-spew" 55 | - "github.com/pmezard/go-difflib" 56 | yaml-modules: 57 | patterns: 58 | - "gopkg.in/yaml.*" 59 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | version: v1 2 | labels: 3 | - label: '📒 Documentation' 4 | matcher: 5 | title: '\b(docs|doc:|\[doc\]|README|typos|comment|documentation)\b' 6 | - label: '☢️ Bug' 7 | matcher: 8 | title: '\b(fix|race|bug|missing|correct)\b' 9 | - label: '🧹 Updates' 10 | matcher: 11 | title: '\b(improve|update|refactor|deprecated|remove|unused|test)\b' 12 | - label: '🤖 Dependencies' 13 | matcher: 14 | title: '\b(bumb|bdependencies)\b' 15 | - label: '✏️ Feature' 16 | matcher: 17 | title: '\b(feature|feat|create|implement|add)\b' 18 | - label: '🤔 Question' 19 | matcher: 20 | title: '\b(question|how)\b' 21 | -------------------------------------------------------------------------------- /.github/release-drafter-template.yml: -------------------------------------------------------------------------------- 1 | name-template: '{{FOLDER}} - v$RESOLVED_VERSION' 2 | tag-template: '{{FOLDER}}/v$RESOLVED_VERSION' 3 | tag-prefix: {{FOLDER}}/v 4 | include-paths: 5 | - {{FOLDER}} 6 | categories: 7 | - title: '❗ Breaking Changes' 8 | labels: 9 | - '❗ BreakingChange' 10 | - title: '🚀 New' 11 | labels: 12 | - '✏️ Feature' 13 | - title: '🧹 Updates' 14 | labels: 15 | - '🧹 Updates' 16 | - '🤖 Dependencies' 17 | - title: '🐛 Fixes' 18 | labels: 19 | - '☢️ Bug' 20 | - title: '📚 Documentation' 21 | labels: 22 | - '📒 Documentation' 23 | change-template: '- $TITLE (#$NUMBER)' 24 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 25 | exclude-contributors: 26 | - dependabot 27 | - dependabot[bot] 28 | version-resolver: 29 | major: 30 | labels: 31 | - 'major' 32 | - '❗ BreakingChange' 33 | minor: 34 | labels: 35 | - 'minor' 36 | - '✏️ Feature' 37 | patch: 38 | labels: 39 | - 'patch' 40 | - '📒 Documentation' 41 | - '☢️ Bug' 42 | - '🤖 Dependencies' 43 | - '🧹 Updates' 44 | default: patch 45 | template: | 46 | $CHANGES 47 | 48 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...{{FOLDER}}/v$RESOLVED_VERSION 49 | 50 | Thank you $CONTRIBUTORS for making this update possible. 51 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | categories: 5 | - title: '❗ Breaking Changes' 6 | labels: 7 | - '❗ BreakingChange' 8 | - title: '🚀 New Features' 9 | labels: 10 | - '✏️ Feature' 11 | - '📝 Proposal' 12 | - title: '🧹 Updates' 13 | labels: 14 | - '🧹 Updates' 15 | - '⚡️ Performance' 16 | - title: '🐛 Bug Fixes' 17 | labels: 18 | - '☢️ Bug' 19 | - title: '🛠️ Maintenance' 20 | labels: 21 | - '🤖 Dependencies' 22 | - title: '📚 Documentation' 23 | labels: 24 | - '📒 Documentation' 25 | - title: 'Other Changes' 26 | labels: 27 | - '*' 28 | -------------------------------------------------------------------------------- /.github/scripts/sync_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Some env variables 5 | BRANCH="main" 6 | REPO_URL="github.com/gofiber/docs.git" 7 | AUTHOR_EMAIL="github-actions[bot]@users.noreply.github.com" 8 | AUTHOR_USERNAME="github-actions[bot]" 9 | VERSION_FILE="contrib_versions.json" 10 | REPO_DIR="contrib" 11 | COMMIT_URL="https://github.com/gofiber/contrib" 12 | DOCUSAURUS_COMMAND="npm run docusaurus -- docs:version:contrib" 13 | 14 | # Set commit author 15 | git config --global user.email "${AUTHOR_EMAIL}" 16 | git config --global user.name "${AUTHOR_USERNAME}" 17 | 18 | git clone https://${TOKEN}@${REPO_URL} fiber-docs 19 | 20 | # Handle push event 21 | if [ "$EVENT" == "push" ]; then 22 | latest_commit=$(git rev-parse --short HEAD) 23 | 24 | for f in $(find . -type f -name "*.md" -not -path "./fiber-docs/*"); do 25 | log_output=$(git log --oneline "${BRANCH}" HEAD~1..HEAD --name-status -- "${f}") 26 | 27 | if [[ $log_output != "" || ! -f "fiber-docs/docs/${REPO_DIR}/$f" ]]; then 28 | mkdir -p fiber-docs/docs/${REPO_DIR}/$(dirname $f) 29 | cp "${f}" fiber-docs/docs/${REPO_DIR}/$f 30 | fi 31 | done 32 | 33 | # Handle release event 34 | elif [ "$EVENT" == "release" ]; then 35 | # Extract package name from tag 36 | package_name="${TAG_NAME%/*}" 37 | major_version="${TAG_NAME#*/}" 38 | major_version="${major_version%%.*}" 39 | 40 | # Form new version name 41 | new_version="${package_name}_${major_version}.x.x" 42 | 43 | cd fiber-docs/ || true 44 | npm ci 45 | 46 | # Check if contrib_versions.json exists and modify it if required 47 | if [[ -f $VERSION_FILE ]]; then 48 | jq --arg new_version "$new_version" 'del(.[] | select(. == $new_version))' $VERSION_FILE > temp.json && mv temp.json $VERSION_FILE 49 | fi 50 | 51 | # Run docusaurus versioning command 52 | $DOCUSAURUS_COMMAND "${new_version}" 53 | 54 | if [[ -f $VERSION_FILE ]]; then 55 | jq 'sort | reverse' ${VERSION_FILE} > temp.json && mv temp.json ${VERSION_FILE} 56 | fi 57 | fi 58 | 59 | # Push changes 60 | cd fiber-docs/ || true 61 | git add . 62 | if [[ $EVENT == "push" ]]; then 63 | git commit -m "Add docs from ${COMMIT_URL}/commit/${latest_commit}" 64 | elif [[ $EVENT == "release" ]]; then 65 | git commit -m "Sync docs for release ${COMMIT_URL}/releases/tag/${TAG_NAME}" 66 | fi 67 | 68 | MAX_RETRIES=5 69 | DELAY=5 70 | retry=0 71 | 72 | while ((retry < MAX_RETRIES)) 73 | do 74 | git push https://${TOKEN}@${REPO_URL} && break 75 | retry=$((retry + 1)) 76 | git pull --rebase 77 | sleep $DELAY 78 | done 79 | 80 | if ((retry == MAX_RETRIES)) 81 | then 82 | echo "Failed to push after $MAX_RETRIES attempts. Exiting with 1." 83 | exit 1 84 | fi 85 | 86 | -------------------------------------------------------------------------------- /.github/workflows/auto-labeler.yml: -------------------------------------------------------------------------------- 1 | name: Auto labeler 2 | on: 3 | issues: 4 | types: [ opened, edited, milestoned ] 5 | pull_request_target: 6 | types: [ opened ] 7 | permissions: 8 | contents: read 9 | issues: write 10 | pull-requests: write 11 | statuses: write 12 | checks: write 13 | jobs: 14 | labeler: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check Labels 18 | id: labeler 19 | uses: fuxingloh/multi-labeler@v4 20 | with: 21 | github-token: ${{secrets.GITHUB_TOKEN}} 22 | -------------------------------------------------------------------------------- /.github/workflows/dependabot_automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: 3 | pull_request 4 | 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | 9 | jobs: 10 | wait_for_checks: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.actor == 'dependabot[bot]' }} 13 | steps: 14 | - name: Wait for check is finished 15 | uses: lewagon/wait-on-check-action@v1.4.0 16 | id: wait_for_checks 17 | with: 18 | ref: ${{ github.event.pull_request.head.sha || github.sha }} 19 | running-workflow-name: wait_for_checks 20 | check-regexp: Tests 21 | repo-token: ${{ secrets.PR_TOKEN }} 22 | wait-interval: 10 23 | dependabot: 24 | needs: [wait_for_checks] 25 | name: Dependabot auto-merge 26 | runs-on: ubuntu-latest 27 | if: ${{ github.actor == 'dependabot[bot]' }} 28 | steps: 29 | - name: Dependabot metadata 30 | id: metadata 31 | uses: dependabot/fetch-metadata@v2.4.0 32 | with: 33 | github-token: "${{ secrets.PR_TOKEN }}" 34 | - name: Enable auto-merge for Dependabot PRs 35 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 36 | run: | 37 | gh pr review --approve "$PR_URL" 38 | gh pr merge --auto --merge "$PR_URL" 39 | env: 40 | PR_URL: ${{github.event.pull_request.html_url}} 41 | GITHUB_TOKEN: ${{secrets.PR_TOKEN}} 42 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: Golangci Lint Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "main" 8 | paths-ignore: 9 | - "**.md" 10 | - LICENSE 11 | - ".github/ISSUE_TEMPLATE/*.yml" 12 | - ".github/dependabot.yml" 13 | pull_request: 14 | branches: 15 | - "*" 16 | paths-ignore: 17 | - "**.md" 18 | - LICENSE 19 | - ".github/ISSUE_TEMPLATE/*.yml" 20 | - ".github/dependabot.yml" 21 | 22 | jobs: 23 | changes: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Fetch Repository 27 | uses: actions/checkout@v5 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Generate filters 32 | id: filter-setup 33 | run: | 34 | filters=$(find . -maxdepth 1 -type d ! -path ./.git ! -path . -exec basename {} \; | grep -v '^\.' | awk '{printf "%s: \"%s/**\"\n", $1, $1}') 35 | echo "filters<> $GITHUB_OUTPUT 36 | echo "$filters" >> $GITHUB_OUTPUT 37 | echo "EOF" >> $GITHUB_OUTPUT 38 | shell: bash 39 | 40 | - name: Filter changes 41 | id: filter 42 | uses: dorny/paths-filter@v3 43 | with: 44 | filters: ${{ steps.filter-setup.outputs.filters }} 45 | outputs: 46 | packages: ${{ steps.filter.outputs.changes || '[]' }} 47 | 48 | lint: 49 | needs: changes 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | package: ${{ fromJSON(needs.changes.outputs.packages || '[]') }} 54 | if: ${{ needs.changes.outputs.packages != '[]' }} 55 | steps: 56 | - name: Fetch Repository 57 | uses: actions/checkout@v5 58 | - uses: actions/setup-go@v5 59 | with: 60 | # NOTE: Keep this in sync with the version from go.mod 61 | go-version: "1.25.x" 62 | cache: false 63 | - name: Run golangci-lint 64 | uses: golangci/golangci-lint-action@v6 65 | with: 66 | args: "--tests=false --timeout=5m" 67 | working-directory: ${{ matrix.package }} 68 | # NOTE: Keep this in sync with the version from .golangci.yml 69 | version: v1.64.7 70 | # NOTE(ldez): temporary workaround 71 | install-mode: goinstall 72 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter (All) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | changes: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pull-requests: read 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v5 17 | 18 | - name: Generate filters 19 | id: filter-setup 20 | run: | 21 | filters=$(find . -maxdepth 1 -type d ! -path ./.git ! -path . -exec basename {} \; | grep -v '^\.' | awk '{printf "%s: \"%s/**\"\n", $1, $1}') 22 | echo "filters<> $GITHUB_OUTPUT 23 | echo "$filters" >> $GITHUB_OUTPUT 24 | echo "EOF" >> $GITHUB_OUTPUT 25 | shell: bash 26 | - name: Filter changes 27 | id: filter 28 | uses: dorny/paths-filter@v3 29 | with: 30 | filters: ${{ steps.filter-setup.outputs.filters }} 31 | 32 | outputs: 33 | packages: ${{ steps.filter.outputs.changes || '[]' }} 34 | 35 | release-drafter: 36 | needs: changes 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 30 39 | if: needs.changes.outputs.packages != '[]' # Ensure job runs only if there are changes 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | strategy: 43 | matrix: 44 | package: ${{ fromJSON(needs.changes.outputs.packages || '[]') }} 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v5 48 | 49 | - name: Generate dynamic config from template 50 | id: generate-config 51 | run: | 52 | folder="${{ matrix.package }}" 53 | sed "s|{{FOLDER}}|$folder|g" .github/release-drafter-template.yml > .github/release-drafter-$folder.yml 54 | echo "config<> $GITHUB_OUTPUT 55 | cat .github/release-drafter-$folder.yml >> $GITHUB_OUTPUT 56 | echo "EOF" >> $GITHUB_OUTPUT 57 | 58 | - name: Use dynamic release-drafter configuration 59 | uses: ReneWerner87/release-drafter@6dec4ceb1fb86b6514f11a2e7a39e1dedce709d0 60 | with: 61 | config: ${{ steps.generate-config.outputs.config }} 62 | -------------------------------------------------------------------------------- /.github/workflows/sync-docs.yml: -------------------------------------------------------------------------------- 1 | name: 'Sync docs' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - '**/*.md' 10 | release: 11 | types: [published] 12 | branches: 13 | - '*/v[0-9]+.[0-9]+.[0-9]+' 14 | 15 | jobs: 16 | sync-docs: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v5 21 | with: 22 | ref: ${{ github.event.pull_request.head.sha }} 23 | fetch-depth: 2 24 | 25 | - name: Setup Node.js environment 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '18' 29 | 30 | - name: Install JQ 31 | run: sudo apt-get install jq 32 | 33 | - name: Sync docs 34 | run: ./.github/scripts/sync_docs.sh 35 | env: 36 | EVENT: ${{ github.event_name }} 37 | TAG_NAME: ${{ github.ref_name }} 38 | TOKEN: ${{ secrets.DOC_SYNC_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/test-casbin.yml: -------------------------------------------------------------------------------- 1 | name: "Test casbin" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'casbin/**' 10 | pull_request: 11 | paths: 12 | - 'casbin/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./casbin 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-circuitbreaker.yml: -------------------------------------------------------------------------------- 1 | name: "Test CircuitBreaker" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'circuitbreaker/**' 10 | pull_request: 11 | paths: 12 | - 'circuitbreaker/**' 13 | - '.github/workflows/**' 14 | 15 | jobs: 16 | Tests: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | go-version: 21 | - 1.25.x 22 | steps: 23 | - name: Fetch Repository 24 | uses: actions/checkout@v5 25 | - name: Install Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: '${{ matrix.go-version }}' 29 | - name: Run Test 30 | working-directory: ./circuitbreaker 31 | run: go test -v -race ./... 32 | -------------------------------------------------------------------------------- /.github/workflows/test-fgprof.yml: -------------------------------------------------------------------------------- 1 | name: "Test Fgprof" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fgprof/**' 10 | pull_request: 11 | paths: 12 | - 'fgprof/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fgprof 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-fiberi18n.yml: -------------------------------------------------------------------------------- 1 | name: "Test fiberi18n" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fiberi18n/**' 10 | pull_request: 11 | paths: 12 | - 'fiberi18n/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fiberi18n 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-fibernewrelic.yml: -------------------------------------------------------------------------------- 1 | name: "Test fibernewrelic" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fibernewrelic/**' 10 | pull_request: 11 | paths: 12 | - 'fibernewrelic/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fibernewrelic 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-fibersentry.yml: -------------------------------------------------------------------------------- 1 | name: "Test fibersentry" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fibersentry/**' 10 | pull_request: 11 | paths: 12 | - 'fibersentry/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fibersentry 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-fiberzap.yml: -------------------------------------------------------------------------------- 1 | name: "Test fiberzap" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fiberzap/**' 10 | pull_request: 11 | paths: 12 | - 'fiberzap/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fiberzap 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-fiberzerolog.yml: -------------------------------------------------------------------------------- 1 | name: "Test fiberzerolog" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'fiberzerolog/**' 10 | pull_request: 11 | paths: 12 | - 'fiberzerolog/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./fiberzerolog 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-hcaptcha.yml: -------------------------------------------------------------------------------- 1 | name: "Test hcaptcha" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'hcaptcha/**' 10 | pull_request: 11 | paths: 12 | - 'hcaptcha/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./hcaptcha 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-jwt.yml: -------------------------------------------------------------------------------- 1 | name: "Test jwt" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'jwt/**' 10 | pull_request: 11 | paths: 12 | - 'jwt/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./jwt 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-loadshed.yml: -------------------------------------------------------------------------------- 1 | name: "Test Loadshed" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - "loadshed/**" 10 | pull_request: 11 | paths: 12 | - "loadshed/**" 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: "${{ matrix.go-version }}" 28 | - name: Run Test 29 | working-directory: ./loadshed 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-monitor.yml: -------------------------------------------------------------------------------- 1 | name: "Test Monitor" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'monitor/**' 10 | pull_request: 11 | paths: 12 | - 'monitor/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./monitor 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-opafiber.yml: -------------------------------------------------------------------------------- 1 | name: "Test opafiber" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'opafiber/**' 10 | pull_request: 11 | paths: 12 | - 'opafiber/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./opafiber 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-otelfiber.yml: -------------------------------------------------------------------------------- 1 | name: "Test otelfiber" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'otelfiber/**' 10 | pull_request: 11 | paths: 12 | - 'otelfiber/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./otelfiber 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-paseto.yml: -------------------------------------------------------------------------------- 1 | name: "Test paseto" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'paseto/**' 10 | pull_request: 11 | paths: 12 | - 'paseto/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./paseto 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-socketio.yml: -------------------------------------------------------------------------------- 1 | name: "Test Socket.io" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - "socketio/**" 10 | pull_request: 11 | paths: 12 | - "socketio/**" 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: "${{ matrix.go-version }}" 28 | - name: Run Test 29 | working-directory: ./socketio 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-swagger.yml: -------------------------------------------------------------------------------- 1 | name: "Test swagger" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'swagger/**' 10 | pull_request: 11 | paths: 12 | - 'swagger/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./swagger 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/test-testcontainers.yml: -------------------------------------------------------------------------------- 1 | name: "Test Testcontainers Services" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'testcontainers/**' 10 | pull_request: 11 | paths: 12 | - 'testcontainers/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | 25 | - name: Install Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: '${{ matrix.go-version }}' 29 | 30 | - name: Run Test 31 | working-directory: ./testcontainers 32 | run: go test -v -race ./... 33 | -------------------------------------------------------------------------------- /.github/workflows/test-websocket.yml: -------------------------------------------------------------------------------- 1 | name: "Test websocket" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'websocket/**' 10 | pull_request: 11 | paths: 12 | - 'websocket/**' 13 | 14 | jobs: 15 | Tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | go-version: 20 | - 1.25.x 21 | steps: 22 | - name: Fetch Repository 23 | uses: actions/checkout@v5 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '${{ matrix.go-version }}' 28 | - name: Run Test 29 | working-directory: ./websocket 30 | run: go test -v -race ./... 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | *.tmp 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # IDE files 16 | .vscode 17 | .DS_Store 18 | .idea 19 | 20 | # Misc 21 | *.fiber.gz 22 | *.fasthttp.gz 23 | *.pprof 24 | *.workspace 25 | 26 | # Dependencies 27 | vendor 28 | /Godeps/ 29 | 30 | # Go workspace file 31 | go.work.sum 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fiber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /casbin/casbin.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gofiber/fiber/v3" 7 | ) 8 | 9 | // Middleware ... 10 | type Middleware struct { 11 | config Config 12 | } 13 | 14 | // New creates an authorization middleware for use in Fiber 15 | func New(config ...Config) *Middleware { 16 | cfg, err := configDefault(config...) 17 | if err != nil { 18 | panic(fmt.Errorf("fiber: casbin middleware error -> %w", err)) 19 | } 20 | 21 | return &Middleware{ 22 | config: cfg, 23 | } 24 | } 25 | 26 | // RequiresPermissions tries to find the current subject and determine if the 27 | // subject has the required permissions according to predefined Casbin policies. 28 | func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) fiber.Handler { 29 | options := optionsDefault(opts...) 30 | 31 | return func(c fiber.Ctx) error { 32 | if len(permissions) == 0 { 33 | return c.Next() 34 | } 35 | 36 | sub := m.config.Lookup(c) 37 | if len(sub) == 0 { 38 | return m.config.Unauthorized(c) 39 | } 40 | 41 | switch options.ValidationRule { 42 | case MatchAllRule: 43 | for _, permission := range permissions { 44 | vals := append([]string{sub}, options.PermissionParser(permission)...) 45 | if ok, err := m.config.Enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil { 46 | return c.SendStatus(fiber.StatusInternalServerError) 47 | } else if !ok { 48 | return m.config.Forbidden(c) 49 | } 50 | } 51 | return c.Next() 52 | case AtLeastOneRule: 53 | for _, permission := range permissions { 54 | vals := append([]string{sub}, options.PermissionParser(permission)...) 55 | if ok, err := m.config.Enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil { 56 | return c.SendStatus(fiber.StatusInternalServerError) 57 | } else if ok { 58 | return c.Next() 59 | } 60 | } 61 | return m.config.Forbidden(c) 62 | } 63 | 64 | return c.Next() 65 | } 66 | } 67 | 68 | // RoutePermission tries to find the current subject and determine if the 69 | // subject has the required permissions according to predefined Casbin policies. 70 | // This method uses http Path and Method as object and action. 71 | func (m *Middleware) RoutePermission() fiber.Handler { 72 | return func(c fiber.Ctx) error { 73 | sub := m.config.Lookup(c) 74 | if len(sub) == 0 { 75 | return m.config.Unauthorized(c) 76 | } 77 | 78 | if ok, err := m.config.Enforcer.Enforce(sub, c.Path(), c.Method()); err != nil { 79 | return c.SendStatus(fiber.StatusInternalServerError) 80 | } else if !ok { 81 | return m.config.Forbidden(c) 82 | } 83 | 84 | return c.Next() 85 | } 86 | } 87 | 88 | // RequiresRoles tries to find the current subject and determine if the 89 | // subject has the required roles according to predefined Casbin policies. 90 | func (m *Middleware) RequiresRoles(roles []string, opts ...Option) fiber.Handler { 91 | options := optionsDefault(opts...) 92 | 93 | return func(c fiber.Ctx) error { 94 | if len(roles) == 0 { 95 | return c.Next() 96 | } 97 | 98 | sub := m.config.Lookup(c) 99 | if len(sub) == 0 { 100 | return m.config.Unauthorized(c) 101 | } 102 | 103 | userRoles, err := m.config.Enforcer.GetRolesForUser(sub) 104 | if err != nil { 105 | return c.SendStatus(fiber.StatusInternalServerError) 106 | } 107 | 108 | switch options.ValidationRule { 109 | case MatchAllRule: 110 | for _, role := range roles { 111 | if !containsString(userRoles, role) { 112 | return m.config.Forbidden(c) 113 | } 114 | } 115 | return c.Next() 116 | case AtLeastOneRule: 117 | for _, role := range roles { 118 | if containsString(userRoles, role) { 119 | return c.Next() 120 | } 121 | } 122 | return m.config.Forbidden(c) 123 | } 124 | 125 | return c.Next() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /casbin/config.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | import ( 4 | "github.com/casbin/casbin/v2" 5 | "github.com/casbin/casbin/v2/persist" 6 | fileadapter "github.com/casbin/casbin/v2/persist/file-adapter" 7 | "github.com/gofiber/fiber/v3" 8 | ) 9 | 10 | // Config holds the configuration for the middleware 11 | type Config struct { 12 | // ModelFilePath is path to model file for Casbin. 13 | // Optional. Default: "./model.conf". 14 | ModelFilePath string 15 | 16 | // PolicyAdapter is an interface for different persistent providers. 17 | // Optional. Default: fileadapter.NewAdapter("./policy.csv"). 18 | PolicyAdapter persist.Adapter 19 | 20 | // Enforcer is an enforcer. If you want to use your own enforcer. 21 | // Optional. Default: nil 22 | Enforcer *casbin.Enforcer 23 | 24 | // Lookup is a function that is used to look up current subject. 25 | // An empty string is considered as unauthenticated user. 26 | // Optional. Default: func(c fiber.Ctx) string { return "" } 27 | Lookup func(fiber.Ctx) string 28 | 29 | // Unauthorized defines the response body for unauthorized responses. 30 | // Optional. Default: func(c fiber.Ctx) error { return c.SendStatus(401) } 31 | Unauthorized fiber.Handler 32 | 33 | // Forbidden defines the response body for forbidden responses. 34 | // Optional. Default: func(c fiber.Ctx) error { return c.SendStatus(403) } 35 | Forbidden fiber.Handler 36 | } 37 | 38 | var ConfigDefault = Config{ 39 | ModelFilePath: "./model.conf", 40 | PolicyAdapter: fileadapter.NewAdapter("./policy.csv"), 41 | Lookup: func(c fiber.Ctx) string { return "" }, 42 | Unauthorized: func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusUnauthorized) }, 43 | Forbidden: func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusForbidden) }, 44 | } 45 | 46 | // Helper function to set default values 47 | func configDefault(config ...Config) (Config, error) { 48 | // Return default config if nothing provided 49 | if len(config) < 1 { 50 | return ConfigDefault, nil 51 | } 52 | 53 | // Override default config 54 | cfg := config[0] 55 | 56 | if cfg.Enforcer == nil { 57 | if cfg.ModelFilePath == "" { 58 | cfg.ModelFilePath = ConfigDefault.ModelFilePath 59 | } 60 | 61 | if cfg.PolicyAdapter == nil { 62 | cfg.PolicyAdapter = ConfigDefault.PolicyAdapter 63 | } 64 | 65 | enforcer, err := casbin.NewEnforcer(cfg.ModelFilePath, cfg.PolicyAdapter) 66 | if err != nil { 67 | return cfg, err 68 | } 69 | 70 | cfg.Enforcer = enforcer 71 | } 72 | 73 | if cfg.Lookup == nil { 74 | cfg.Lookup = ConfigDefault.Lookup 75 | } 76 | 77 | if cfg.Unauthorized == nil { 78 | cfg.Unauthorized = ConfigDefault.Unauthorized 79 | } 80 | 81 | if cfg.Forbidden == nil { 82 | cfg.Forbidden = ConfigDefault.Forbidden 83 | } 84 | 85 | return cfg, nil 86 | } 87 | -------------------------------------------------------------------------------- /casbin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/casbin 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/casbin/casbin/v2 v2.128.0 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.2.0 // indirect 12 | github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect 13 | github.com/casbin/govaluate v1.3.0 // indirect 14 | github.com/gofiber/schema v1.6.0 // indirect 15 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 16 | github.com/google/uuid v1.6.0 // indirect 17 | github.com/klauspost/compress v1.18.0 // indirect 18 | github.com/mattn/go-colorable v0.1.14 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/philhofer/fwd v1.2.0 // indirect 21 | github.com/tinylib/msgp v1.4.0 // indirect 22 | github.com/valyala/bytebufferpool v1.0.0 // indirect 23 | github.com/valyala/fasthttp v1.65.0 // indirect 24 | golang.org/x/crypto v0.42.0 // indirect 25 | golang.org/x/net v0.44.0 // indirect 26 | golang.org/x/sys v0.36.0 // indirect 27 | golang.org/x/text v0.29.0 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /casbin/options.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | import "strings" 4 | 5 | const ( 6 | MatchAllRule ValidationRule = iota 7 | AtLeastOneRule 8 | ) 9 | 10 | var OptionsDefault = Options{ 11 | ValidationRule: MatchAllRule, 12 | PermissionParser: PermissionParserWithSeperator(":"), 13 | } 14 | 15 | type ( 16 | ValidationRule int 17 | // PermissionParserFunc is used for parsing the permission 18 | // to extract object and action usually 19 | PermissionParserFunc func(str string) []string 20 | OptionFunc func(*Options) 21 | // Option specifies casbin configuration options. 22 | Option interface { 23 | apply(*Options) 24 | } 25 | // Options holds Options of middleware 26 | Options struct { 27 | ValidationRule ValidationRule 28 | PermissionParser PermissionParserFunc 29 | } 30 | ) 31 | 32 | func (of OptionFunc) apply(o *Options) { 33 | of(o) 34 | } 35 | 36 | func WithValidationRule(vr ValidationRule) Option { 37 | return OptionFunc(func(o *Options) { 38 | o.ValidationRule = vr 39 | }) 40 | } 41 | 42 | func WithPermissionParser(pp PermissionParserFunc) Option { 43 | return OptionFunc(func(o *Options) { 44 | o.PermissionParser = pp 45 | }) 46 | } 47 | 48 | func PermissionParserWithSeperator(sep string) PermissionParserFunc { 49 | return func(str string) []string { 50 | return strings.Split(str, sep) 51 | } 52 | } 53 | 54 | // Helper function to set default values 55 | func optionsDefault(opts ...Option) Options { 56 | cfg := OptionsDefault 57 | 58 | for _, opt := range opts { 59 | opt.apply(&cfg) 60 | } 61 | 62 | return cfg 63 | } 64 | -------------------------------------------------------------------------------- /casbin/utils.go: -------------------------------------------------------------------------------- 1 | package casbin 2 | 3 | func containsString(s []string, v string) bool { 4 | for _, vv := range s { 5 | if vv == v { 6 | return true 7 | } 8 | } 9 | return false 10 | } 11 | 12 | func stringSliceToInterfaceSlice(s []string) []interface{} { 13 | res := make([]interface{}, len(s)) 14 | for i, v := range s { 15 | res[i] = v 16 | } 17 | return res 18 | } 19 | -------------------------------------------------------------------------------- /circuitbreaker/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/circuitbreaker 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/stretchr/testify v1.11.1 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.2.0 // indirect 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/gofiber/schema v1.6.0 // indirect 14 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 15 | github.com/google/uuid v1.6.0 // indirect 16 | github.com/klauspost/compress v1.18.0 // indirect 17 | github.com/mattn/go-colorable v0.1.14 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/philhofer/fwd v1.2.0 // indirect 20 | github.com/pmezard/go-difflib v1.0.0 // indirect 21 | github.com/tinylib/msgp v1.4.0 // indirect 22 | github.com/valyala/bytebufferpool v1.0.0 // indirect 23 | github.com/valyala/fasthttp v1.65.0 // indirect 24 | golang.org/x/crypto v0.42.0 // indirect 25 | golang.org/x/net v0.44.0 // indirect 26 | golang.org/x/sys v0.36.0 // indirect 27 | golang.org/x/text v0.29.0 // indirect 28 | gopkg.in/yaml.v3 v3.0.1 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /circuitbreaker/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 2 | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 6 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0= 8 | github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg= 9 | github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= 10 | github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= 11 | github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s= 12 | github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E= 13 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 14 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 15 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 16 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 17 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 18 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 19 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 | github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= 22 | github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo= 26 | github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= 27 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 28 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 29 | github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= 30 | github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= 31 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 32 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 33 | github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8= 34 | github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4= 35 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 36 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 37 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 38 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 39 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 40 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 41 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 42 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 45 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 46 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 47 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 51 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | -------------------------------------------------------------------------------- /fgprof/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: fgprof 3 | --- 4 | 5 | # Fgprof 6 | 7 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=fgprof*) 8 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 9 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20Fgprof/badge.svg) 10 | 11 | [fgprof](https://github.com/felixge/fgprof) support for Fiber. 12 | 13 | **Note: Requires Go 1.25 and above** 14 | 15 | ## Install 16 | 17 | This middleware supports Fiber v3. 18 | 19 | Using fgprof to profiling your Fiber app. 20 | 21 | ``` 22 | go get -u github.com/gofiber/fiber/v3 23 | go get -u github.com/gofiber/contrib/fgprof 24 | ``` 25 | 26 | ## Config 27 | 28 | | Property | Type | Description | Default | 29 | |----------|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------| 30 | | Next | `func(c *fiber.Ctx) bool` | A function to skip this middleware when returned `true`. | `nil` | 31 | | Prefix | `string`. | Prefix defines a URL prefix added before "/debug/fgprof". Note that it should start with (but not end with) a slash. Example: "/federated-fiber" | `""` | 32 | 33 | ## Example 34 | 35 | ```go 36 | package main 37 | 38 | import ( 39 | "log" 40 | 41 | "github.com/gofiber/contrib/fgprof" 42 | "github.com/gofiber/fiber/v3" 43 | ) 44 | 45 | func main() { 46 | app := fiber.New() 47 | app.Use(fgprof.New()) 48 | app.Get("/", func(c *fiber.Ctx) error { 49 | return c.SendString("OK") 50 | }) 51 | log.Fatal(app.Listen(":3000")) 52 | } 53 | ``` 54 | 55 | ```bash 56 | go tool pprof -http=:8080 http://localhost:3000/debug/fgprof 57 | ``` 58 | -------------------------------------------------------------------------------- /fgprof/config.go: -------------------------------------------------------------------------------- 1 | package fgprof 2 | 3 | import "github.com/gofiber/fiber/v3" 4 | 5 | type Config struct { 6 | // Next defines a function to skip this middleware when returned true. 7 | // 8 | // Optional. Default: nil 9 | Next func(c fiber.Ctx) bool 10 | 11 | // Prefix is the path where the fprof endpoints will be mounted. 12 | // Default Path is "/debug/fgprof" 13 | // 14 | // Optional. Default: "" 15 | Prefix string 16 | } 17 | 18 | // ConfigDefault is the default config 19 | var ConfigDefault = Config{ 20 | Next: nil, 21 | } 22 | 23 | func configDefault(config ...Config) Config { 24 | // Return default config if nothing provided 25 | if len(config) < 1 { 26 | return ConfigDefault 27 | } 28 | 29 | // Override default config 30 | cfg := config[0] 31 | 32 | // Set default values 33 | if cfg.Next == nil { 34 | cfg.Next = ConfigDefault.Next 35 | } 36 | 37 | return cfg 38 | } 39 | -------------------------------------------------------------------------------- /fgprof/fgprof.go: -------------------------------------------------------------------------------- 1 | package fgprof 2 | 3 | import ( 4 | "github.com/felixge/fgprof" 5 | "github.com/gofiber/fiber/v3" 6 | "github.com/gofiber/fiber/v3/middleware/adaptor" 7 | ) 8 | 9 | func New(conf ...Config) fiber.Handler { 10 | // Set default config 11 | cfg := configDefault(conf...) 12 | 13 | fgProfPath := cfg.Prefix + "/debug/fgprof" 14 | 15 | var fgprofHandler = adaptor.HTTPHandler(fgprof.Handler()) 16 | 17 | // Return new handler 18 | return func(c fiber.Ctx) error { 19 | // Don't execute middleware if Next returns true 20 | if cfg.Next != nil && cfg.Next(c) { 21 | return c.Next() 22 | } 23 | 24 | if c.Path() == fgProfPath { 25 | return fgprofHandler(c) 26 | } 27 | return c.Next() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /fgprof/fgprof_test.go: -------------------------------------------------------------------------------- 1 | package fgprof 2 | 3 | import ( 4 | "io" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/gofiber/fiber/v3" 11 | ) 12 | 13 | // go test -run Test_Non_Fgprof_Path 14 | func Test_Non_Fgprof_Path(t *testing.T) { 15 | app := fiber.New() 16 | app.Use(New()) 17 | 18 | app.Get("/", func(c fiber.Ctx) error { 19 | return c.SendString("escaped") 20 | }) 21 | 22 | resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) 23 | assert.Equal(t, nil, err) 24 | assert.Equal(t, 200, resp.StatusCode) 25 | 26 | body, err := io.ReadAll(resp.Body) 27 | assert.Equal(t, nil, err) 28 | assert.Equal(t, "escaped", string(body)) 29 | } 30 | 31 | // go test -run Test_Non_Fgprof_Path_WithPrefix 32 | func Test_Non_Fgprof_Path_WithPrefix(t *testing.T) { 33 | app := fiber.New() 34 | app.Use(New(Config{ 35 | Prefix: "/prefix", 36 | })) 37 | 38 | app.Get("/", func(c fiber.Ctx) error { 39 | return c.SendString("escaped") 40 | }) 41 | 42 | resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) 43 | assert.Equal(t, nil, err) 44 | assert.Equal(t, 200, resp.StatusCode) 45 | 46 | body, err := io.ReadAll(resp.Body) 47 | assert.Equal(t, nil, err) 48 | assert.Equal(t, "escaped", string(body)) 49 | } 50 | 51 | // go test -run Test_Fgprof_Path 52 | func Test_Fgprof_Path(t *testing.T) { 53 | app := fiber.New() 54 | app.Use(New()) 55 | 56 | // Default fgprof interval is 30 seconds 57 | resp, err := app.Test(httptest.NewRequest("GET", "/debug/fgprof?seconds=1", nil), fiber.TestConfig{Timeout: 1500}) 58 | assert.Equal(t, nil, err) 59 | assert.Equal(t, 200, resp.StatusCode) 60 | } 61 | 62 | // go test -run Test_Fgprof_Path_WithPrefix 63 | func Test_Fgprof_Path_WithPrefix(t *testing.T) { 64 | app := fiber.New() 65 | app.Use(New(Config{ 66 | Prefix: "/test", 67 | })) 68 | app.Get("/", func(c fiber.Ctx) error { 69 | return c.SendString("escaped") 70 | }) 71 | 72 | // Non fgprof prefix path 73 | resp, err := app.Test(httptest.NewRequest("GET", "/prefix/debug/fgprof?seconds=1", nil), fiber.TestConfig{Timeout: 1500}) 74 | assert.Equal(t, nil, err) 75 | assert.Equal(t, 404, resp.StatusCode) 76 | // Fgprof prefix path 77 | resp, err = app.Test(httptest.NewRequest("GET", "/test/debug/fgprof?seconds=1", nil), fiber.TestConfig{Timeout: 1500}) 78 | assert.Equal(t, nil, err) 79 | assert.Equal(t, 200, resp.StatusCode) 80 | } 81 | 82 | // go test -run Test_Fgprof_Next 83 | func Test_Fgprof_Next(t *testing.T) { 84 | app := fiber.New() 85 | 86 | app.Use(New(Config{ 87 | Next: func(_ fiber.Ctx) bool { 88 | return true 89 | }, 90 | })) 91 | 92 | resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/pprof/", nil)) 93 | assert.Equal(t, nil, err) 94 | assert.Equal(t, 404, resp.StatusCode) 95 | } 96 | 97 | // go test -run Test_Fgprof_Next_WithPrefix 98 | func Test_Fgprof_Next_WithPrefix(t *testing.T) { 99 | app := fiber.New() 100 | 101 | app.Use(New(Config{ 102 | Next: func(_ fiber.Ctx) bool { 103 | return true 104 | }, 105 | Prefix: "/federated-fiber", 106 | })) 107 | 108 | resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil)) 109 | assert.Equal(t, nil, err) 110 | assert.Equal(t, 404, resp.StatusCode) 111 | } 112 | -------------------------------------------------------------------------------- /fgprof/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fgprof 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/felixge/fgprof v0.9.5 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.2.0 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/gofiber/schema v1.6.0 // indirect 15 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 16 | github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/tinylib/msgp v1.4.0 // indirect 24 | github.com/valyala/bytebufferpool v1.0.0 // indirect 25 | github.com/valyala/fasthttp v1.65.0 // indirect 26 | golang.org/x/crypto v0.42.0 // indirect 27 | golang.org/x/net v0.44.0 // indirect 28 | golang.org/x/sys v0.36.0 // indirect 29 | golang.org/x/text v0.29.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /fiberi18n/config.go: -------------------------------------------------------------------------------- 1 | package fiberi18n 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/gofiber/utils/v2" 9 | "github.com/nicksnyder/go-i18n/v2/i18n" 10 | "golang.org/x/text/language" 11 | "gopkg.in/yaml.v2" 12 | ) 13 | 14 | type Config struct { 15 | // Next defines a function to skip this middleware when returned true. 16 | // 17 | // Optional. Default: nil 18 | Next func(c fiber.Ctx) bool 19 | 20 | // RootPath is i18n template folder path 21 | // 22 | // Default: ./example/localize 23 | RootPath string 24 | 25 | // AcceptLanguages is a collection of languages that can be processed 26 | // 27 | // Optional. Default: []language.Tag{language.Chinese, language.English} 28 | AcceptLanguages []language.Tag 29 | 30 | // FormatBundleFile is type of template file. 31 | // 32 | // Optional. Default: "yaml" 33 | FormatBundleFile string 34 | 35 | // DefaultLanguage is the default returned language type 36 | // 37 | // Optional. Default: language.English 38 | DefaultLanguage language.Tag 39 | 40 | // Loader implements the Loader interface, which defines how to read the file. 41 | // We provide both os.ReadFile and embed.FS.ReadFile 42 | // Optional. Default: LoaderFunc(os.ReadFile) 43 | Loader Loader 44 | 45 | // UnmarshalFunc for decoding template files 46 | // 47 | // Optional. Default: yaml.Unmarshal 48 | UnmarshalFunc i18n.UnmarshalFunc 49 | 50 | // LangHandler is used to get the kind of language handled by fiber.Ctx and defaultLang 51 | // 52 | // Optional. Default: The language type is retrieved from the request header: `Accept-Language` or query param : `lang` 53 | LangHandler func(ctx fiber.Ctx, defaultLang string) string 54 | 55 | bundle *i18n.Bundle 56 | localizerMap *sync.Map 57 | mu sync.Mutex 58 | } 59 | 60 | type Loader interface { 61 | LoadMessage(path string) ([]byte, error) 62 | } 63 | 64 | type LoaderFunc func(path string) ([]byte, error) 65 | 66 | func (f LoaderFunc) LoadMessage(path string) ([]byte, error) { 67 | return f(path) 68 | } 69 | 70 | var ConfigDefault = &Config{ 71 | Next: nil, 72 | RootPath: "./example/localize", 73 | DefaultLanguage: language.English, 74 | AcceptLanguages: []language.Tag{language.Chinese, language.English}, 75 | FormatBundleFile: "yaml", 76 | UnmarshalFunc: yaml.Unmarshal, 77 | Loader: LoaderFunc(os.ReadFile), 78 | LangHandler: defaultLangHandler, 79 | } 80 | 81 | func defaultLangHandler(c fiber.Ctx, defaultLang string) string { 82 | if c == nil || c.Request() == nil { 83 | return defaultLang 84 | } 85 | var lang string 86 | lang = utils.CopyString(c.Query("lang")) 87 | if lang != "" { 88 | return lang 89 | } 90 | lang = utils.CopyString(c.Get("Accept-Language")) 91 | if lang != "" { 92 | return lang 93 | } 94 | 95 | return defaultLang 96 | } 97 | 98 | func configDefault(config ...*Config) *Config { 99 | // Return default config if nothing provided 100 | if len(config) == 0 { 101 | return ConfigDefault 102 | } 103 | 104 | // Override default config 105 | cfg := config[0] 106 | 107 | if cfg.Next == nil { 108 | cfg.Next = ConfigDefault.Next 109 | } 110 | 111 | if cfg.RootPath == "" { 112 | cfg.RootPath = ConfigDefault.RootPath 113 | } 114 | 115 | if cfg.DefaultLanguage == language.Und { 116 | cfg.DefaultLanguage = ConfigDefault.DefaultLanguage 117 | } 118 | 119 | if cfg.UnmarshalFunc == nil { 120 | cfg.UnmarshalFunc = ConfigDefault.UnmarshalFunc 121 | } 122 | 123 | if cfg.FormatBundleFile == "" { 124 | cfg.FormatBundleFile = ConfigDefault.FormatBundleFile 125 | } 126 | 127 | if cfg.AcceptLanguages == nil { 128 | cfg.AcceptLanguages = ConfigDefault.AcceptLanguages 129 | } 130 | 131 | if cfg.Loader == nil { 132 | cfg.Loader = ConfigDefault.Loader 133 | } 134 | 135 | if cfg.UnmarshalFunc == nil { 136 | cfg.UnmarshalFunc = ConfigDefault.UnmarshalFunc 137 | } 138 | 139 | if cfg.LangHandler == nil { 140 | cfg.LangHandler = ConfigDefault.LangHandler 141 | } 142 | 143 | return cfg 144 | } 145 | -------------------------------------------------------------------------------- /fiberi18n/embed.go: -------------------------------------------------------------------------------- 1 | //go:build go1.16 2 | 3 | package fiberi18n 4 | 5 | import "embed" 6 | 7 | type EmbedLoader struct { 8 | FS embed.FS 9 | } 10 | 11 | func (e *EmbedLoader) LoadMessage(path string) ([]byte, error) { 12 | return e.FS.ReadFile(path) 13 | } 14 | -------------------------------------------------------------------------------- /fiberi18n/embed_test.go: -------------------------------------------------------------------------------- 1 | package fiberi18n 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | 13 | "github.com/gofiber/fiber/v3" 14 | "github.com/nicksnyder/go-i18n/v2/i18n" 15 | "golang.org/x/text/language" 16 | ) 17 | 18 | //go:embed example/localizeJSON/* 19 | var fs embed.FS 20 | 21 | func newEmbedServer() *fiber.App { 22 | app := fiber.New() 23 | app.Use(New(&Config{ 24 | Loader: &EmbedLoader{fs}, 25 | UnmarshalFunc: json.Unmarshal, 26 | RootPath: "./example/localizeJSON/", 27 | FormatBundleFile: "json", 28 | })) 29 | app.Get("/", func(ctx fiber.Ctx) error { 30 | return ctx.SendString(MustLocalize(ctx, "welcome")) 31 | }) 32 | app.Get("/:name", func(ctx fiber.Ctx) error { 33 | return ctx.SendString(MustLocalize(ctx, &i18n.LocalizeConfig{ 34 | MessageID: "welcomeWithName", 35 | TemplateData: map[string]string{ 36 | "name": ctx.Params("name"), 37 | }, 38 | })) 39 | }) 40 | return app 41 | } 42 | 43 | var embedApp = newEmbedServer() 44 | 45 | func request(lang language.Tag, name string) (*http.Response, error) { 46 | path := "/" + name 47 | req, _ := http.NewRequestWithContext(context.Background(), "GET", path, nil) 48 | req.Header.Add("Accept-Language", lang.String()) 49 | req.Method = "GET" 50 | req.RequestURI = path 51 | resp, err := embedApp.Test(req) 52 | return resp, err 53 | } 54 | 55 | func TestEmbedLoader_LoadMessage(t *testing.T) { 56 | t.Parallel() 57 | type args struct { 58 | lang language.Tag 59 | name string 60 | } 61 | tests := []struct { 62 | name string 63 | args args 64 | want string 65 | }{ 66 | { 67 | name: "hello world", 68 | args: args{ 69 | name: "", 70 | lang: language.English, 71 | }, 72 | want: "hello", 73 | }, 74 | { 75 | name: "hello alex", 76 | args: args{ 77 | name: "", 78 | lang: language.Chinese, 79 | }, 80 | want: "你好", 81 | }, 82 | { 83 | name: "hello alex", 84 | args: args{ 85 | name: "alex", 86 | lang: language.English, 87 | }, 88 | want: "hello alex", 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | got, err := request(tt.args.lang, tt.args.name) 94 | assert.Equal(t, err, nil) 95 | body, err := io.ReadAll(got.Body) 96 | assert.Equal(t, err, nil) 97 | assert.Equal(t, tt.want, string(body)) 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /fiberi18n/example/localize/en.yaml: -------------------------------------------------------------------------------- 1 | welcome: hello 2 | welcomeWithName: hello {{ .name }} 3 | -------------------------------------------------------------------------------- /fiberi18n/example/localize/zh.yaml: -------------------------------------------------------------------------------- 1 | welcome: 你好 2 | welcomeWithName: 你好 {{ .name }} 3 | -------------------------------------------------------------------------------- /fiberi18n/example/localizeJSON/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "hello", 3 | "welcomeWithName": "hello {{ .name }}" 4 | } 5 | -------------------------------------------------------------------------------- /fiberi18n/example/localizeJSON/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "你好", 3 | "welcomeWithName": "你好 {{ .name }}" 4 | } 5 | -------------------------------------------------------------------------------- /fiberi18n/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gofiber/contrib/fiberi18n/v2" 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/nicksnyder/go-i18n/v2/i18n" 9 | "golang.org/x/text/language" 10 | ) 11 | 12 | func main() { 13 | app := fiber.New() 14 | app.Use( 15 | fiberi18n.New(&fiberi18n.Config{ 16 | RootPath: "./localize", 17 | AcceptLanguages: []language.Tag{language.Chinese, language.English}, 18 | DefaultLanguage: language.Chinese, 19 | }), 20 | ) 21 | app.Get("/", func(c fiber.Ctx) error { 22 | localize, err := fiberi18n.Localize(c, "welcome") 23 | if err != nil { 24 | return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) 25 | } 26 | return c.SendString(localize) 27 | }) 28 | app.Get("/:name", func(ctx fiber.Ctx) error { 29 | return ctx.SendString(fiberi18n.MustLocalize(ctx, &i18n.LocalizeConfig{ 30 | MessageID: "welcomeWithName", 31 | TemplateData: map[string]string{ 32 | "name": ctx.Params("name"), 33 | }, 34 | })) 35 | }) 36 | log.Fatal(app.Listen(":3000")) 37 | } 38 | -------------------------------------------------------------------------------- /fiberi18n/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fiberi18n/v2 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/gofiber/utils/v2 v2.0.0-rc.1 8 | github.com/nicksnyder/go-i18n/v2 v2.4.1 9 | github.com/stretchr/testify v1.11.1 10 | golang.org/x/text v0.29.0 11 | gopkg.in/yaml.v2 v2.4.0 12 | ) 13 | 14 | require ( 15 | github.com/andybalholm/brotli v1.2.0 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/gofiber/schema v1.6.0 // indirect 18 | github.com/google/uuid v1.6.0 // indirect 19 | github.com/klauspost/compress v1.18.0 // indirect 20 | github.com/mattn/go-colorable v0.1.14 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/philhofer/fwd v1.2.0 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/tinylib/msgp v1.4.0 // indirect 25 | github.com/valyala/bytebufferpool v1.0.0 // indirect 26 | github.com/valyala/fasthttp v1.65.0 // indirect 27 | golang.org/x/crypto v0.42.0 // indirect 28 | golang.org/x/net v0.44.0 // indirect 29 | golang.org/x/sys v0.36.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /fiberi18n/i18n.go: -------------------------------------------------------------------------------- 1 | package fiberi18n 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "sync" 7 | 8 | "github.com/gofiber/fiber/v3/log" 9 | 10 | "github.com/gofiber/fiber/v3" 11 | "github.com/nicksnyder/go-i18n/v2/i18n" 12 | ) 13 | 14 | const localsKey = "fiberi18n" 15 | 16 | // New creates a new middleware handler 17 | func New(config ...*Config) fiber.Handler { 18 | cfg := configDefault(config...) 19 | // init bundle 20 | bundle := i18n.NewBundle(cfg.DefaultLanguage) 21 | bundle.RegisterUnmarshalFunc(cfg.FormatBundleFile, cfg.UnmarshalFunc) 22 | cfg.bundle = bundle 23 | 24 | cfg.loadMessages() 25 | cfg.initLocalizerMap() 26 | 27 | return func(c fiber.Ctx) error { 28 | if cfg.Next != nil && cfg.Next(c) { 29 | return c.Next() 30 | } 31 | c.Locals(localsKey, cfg) 32 | return c.Next() 33 | } 34 | } 35 | 36 | func (c *Config) loadMessage(filepath string) { 37 | buf, err := c.Loader.LoadMessage(filepath) 38 | if err != nil { 39 | panic(err) 40 | } 41 | if _, err := c.bundle.ParseMessageFileBytes(buf, filepath); err != nil { 42 | panic(err) 43 | } 44 | } 45 | 46 | func (c *Config) loadMessages() *Config { 47 | for _, lang := range c.AcceptLanguages { 48 | bundleFilePath := fmt.Sprintf("%s.%s", lang.String(), c.FormatBundleFile) 49 | filepath := path.Join(c.RootPath, bundleFilePath) 50 | c.loadMessage(filepath) 51 | } 52 | return c 53 | } 54 | 55 | func (c *Config) initLocalizerMap() { 56 | localizerMap := &sync.Map{} 57 | 58 | for _, lang := range c.AcceptLanguages { 59 | s := lang.String() 60 | localizerMap.Store(s, i18n.NewLocalizer(c.bundle, s)) 61 | } 62 | 63 | lang := c.DefaultLanguage.String() 64 | if _, ok := localizerMap.Load(lang); !ok { 65 | localizerMap.Store(lang, i18n.NewLocalizer(c.bundle, lang)) 66 | } 67 | c.mu.Lock() 68 | c.localizerMap = localizerMap 69 | c.mu.Unlock() 70 | } 71 | 72 | /* 73 | MustLocalize get the i18n message without error handling 74 | 75 | param is one of these type: messageID, *i18n.LocalizeConfig 76 | Example: 77 | MustLocalize(ctx, "hello") // messageID is hello 78 | MustLocalize(ctx, &i18n.LocalizeConfig{ 79 | MessageID: "welcomeWithName", 80 | TemplateData: map[string]string{ 81 | "name": context.Param("name"), 82 | }, 83 | }) 84 | */ 85 | func MustLocalize(ctx fiber.Ctx, params interface{}) string { 86 | message, err := Localize(ctx, params) 87 | if err != nil { 88 | panic(err) 89 | } 90 | return message 91 | } 92 | 93 | /* 94 | Localize get the i18n message 95 | 96 | param is one of these type: messageID, *i18n.LocalizeConfig 97 | Example: 98 | Localize(ctx, "hello") // messageID is hello 99 | Localize(ctx, &i18n.LocalizeConfig{ 100 | MessageID: "welcomeWithName", 101 | TemplateData: map[string]string{ 102 | "name": context.Param("name"), 103 | }, 104 | }) 105 | */ 106 | func Localize(ctx fiber.Ctx, params interface{}) (string, error) { 107 | local := ctx.Locals(localsKey) 108 | if local == nil { 109 | return "", fmt.Errorf("i18n.Localize error: %v", "Config is nil") 110 | } 111 | 112 | appCfg, ok := local.(*Config) 113 | if !ok { 114 | return "", fmt.Errorf("i18n.Localize error: %v", "Config is not *Config type") 115 | } 116 | 117 | lang := appCfg.LangHandler(ctx, appCfg.DefaultLanguage.String()) 118 | localizer, _ := appCfg.localizerMap.Load(lang) 119 | 120 | if localizer == nil { 121 | defaultLang := appCfg.DefaultLanguage.String() 122 | localizer, _ = appCfg.localizerMap.Load(defaultLang) 123 | } 124 | 125 | var localizeConfig *i18n.LocalizeConfig 126 | switch paramValue := params.(type) { 127 | case string: 128 | localizeConfig = &i18n.LocalizeConfig{MessageID: paramValue} 129 | case *i18n.LocalizeConfig: 130 | localizeConfig = paramValue 131 | } 132 | 133 | message, err := localizer.(*i18n.Localizer).Localize(localizeConfig) 134 | if err != nil { 135 | log.Errorf("i18n.Localize error: %v", err) 136 | return "", fmt.Errorf("i18n.Localize error: %v", err) 137 | } 138 | return message, nil 139 | } 140 | -------------------------------------------------------------------------------- /fibernewrelic/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: fibernewrelic 3 | --- 4 | 5 | # Fibernewrelic 6 | 7 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=fibernewrelic*) 8 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 9 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20fibernewrelic/badge.svg) 10 | 11 | [NewRelic](https://github.com/newrelic/go-agent) support for Fiber. 12 | 13 | **Note: Requires Go 1.25 and above** 14 | 15 | ## Install 16 | 17 | ``` 18 | go get -u github.com/gofiber/fiber/v3 19 | go get -u github.com/gofiber/contrib/fibernewrelic 20 | ``` 21 | 22 | ## Signature 23 | 24 | ```go 25 | fibernewrelic.New(config fibernewrelic.Config) fiber.Handler 26 | ``` 27 | 28 | ## Config 29 | 30 | | Property | Type | Description | Default | 31 | |:-----------------------|:-----------------|:------------------------------------------------------------|:--------------------------------| 32 | | License | `string` | Required - New Relic License Key | `""` | 33 | | AppName | `string` | New Relic Application Name | `fiber-api` | 34 | | Enabled | `bool` | Enable/Disable New Relic | `false` | 35 | | ~~TransportType~~ | ~~`string`~~ | ~~Can be HTTP or HTTPS~~ (Deprecated) | ~~`"HTTP"`~~ | 36 | | Application | `Application` | Existing New Relic App | `nil` | 37 | | ErrorStatusCodeHandler | `func(c *fiber.Ctx, err error) int` | If you want to change newrelic status code, you can use it. | `DefaultErrorStatusCodeHandler` | 38 | | Next | `func(c *fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | 39 | 40 | 41 | ## Usage 42 | 43 | ```go 44 | package main 45 | 46 | import ( 47 | "github.com/gofiber/fiber/v3" 48 | "github.com/gofiber/contrib/fibernewrelic" 49 | ) 50 | 51 | func main() { 52 | app := fiber.New() 53 | 54 | app.Get("/", func(ctx *fiber.Ctx) error { 55 | return ctx.SendStatus(200) 56 | }) 57 | 58 | cfg := fibernewrelic.Config{ 59 | License: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 60 | AppName: "MyCustomApi", 61 | Enabled: true, 62 | } 63 | 64 | app.Use(fibernewrelic.New(cfg)) 65 | 66 | app.Listen(":8080") 67 | } 68 | ``` 69 | 70 | ## Usage with existing New Relic application 71 | 72 | ```go 73 | package main 74 | 75 | import ( 76 | "github.com/gofiber/fiber/v3" 77 | "github.com/gofiber/contrib/fibernewrelic" 78 | "github.com/newrelic/go-agent/v3/newrelic" 79 | ) 80 | 81 | func main() { 82 | newrelicApp, err := newrelic.NewApplication( 83 | newrelic.ConfigAppName("MyCustomApi"), 84 | newrelic.ConfigLicense("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 85 | newrelic.ConfigEnabled(true), 86 | ) 87 | 88 | app := fiber.New() 89 | 90 | app.Get("/", func(ctx *fiber.Ctx) error { 91 | return ctx.SendStatus(200) 92 | }) 93 | 94 | app.Get("/foo", func(ctx *fiber.Ctx) error { 95 | txn := newrelic.FromContext(ctx) 96 | segment := txn.StartSegment("foo segment") 97 | defer segment.End() 98 | 99 | // do foo 100 | 101 | return nil 102 | }) 103 | 104 | cfg := fibernewrelic.Config{ 105 | Application: newrelicApp, 106 | } 107 | 108 | app.Use(fibernewrelic.New(cfg)) 109 | 110 | app.Listen(":8080") 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /fibernewrelic/fiber.go: -------------------------------------------------------------------------------- 1 | package fibernewrelic 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/gofiber/utils/v2" 9 | 10 | "github.com/gofiber/fiber/v3" 11 | "github.com/newrelic/go-agent/v3/newrelic" 12 | ) 13 | 14 | type Config struct { 15 | // License parameter is required to initialize newrelic application 16 | License string 17 | // AppName parameter passed to set app name, default is fiber-api 18 | AppName string 19 | // Enabled parameter passed to enable/disable newrelic 20 | Enabled bool 21 | // TransportType can be HTTP or HTTPS, default is HTTP 22 | // Deprecated: The Transport type now acquiring from request URL scheme internally 23 | TransportType string 24 | // Application field is required to use an existing newrelic application 25 | Application *newrelic.Application 26 | // ErrorStatusCodeHandler is executed when an error is returned from handler 27 | // Optional. Default: DefaultErrorStatusCodeHandler 28 | ErrorStatusCodeHandler func(c fiber.Ctx, err error) int 29 | // Next defines a function to skip this middleware when returned true. 30 | // Optional. Default: nil 31 | Next func(c fiber.Ctx) bool 32 | } 33 | 34 | var ConfigDefault = Config{ 35 | Application: nil, 36 | License: "", 37 | AppName: "fiber-api", 38 | Enabled: false, 39 | ErrorStatusCodeHandler: DefaultErrorStatusCodeHandler, 40 | Next: nil, 41 | } 42 | 43 | func New(cfg Config) fiber.Handler { 44 | var app *newrelic.Application 45 | var err error 46 | 47 | if cfg.ErrorStatusCodeHandler == nil { 48 | cfg.ErrorStatusCodeHandler = ConfigDefault.ErrorStatusCodeHandler 49 | } 50 | 51 | if cfg.Application != nil { 52 | app = cfg.Application 53 | } else { 54 | if cfg.AppName == "" { 55 | cfg.AppName = ConfigDefault.AppName 56 | } 57 | 58 | if cfg.License == "" { 59 | panic(fmt.Errorf("unable to create New Relic Application -> License can not be empty")) 60 | } 61 | 62 | app, err = newrelic.NewApplication( 63 | newrelic.ConfigAppName(cfg.AppName), 64 | newrelic.ConfigLicense(cfg.License), 65 | newrelic.ConfigEnabled(cfg.Enabled), 66 | ) 67 | 68 | if err != nil { 69 | panic(fmt.Errorf("unable to create New Relic Application -> %w", err)) 70 | } 71 | } 72 | 73 | return func(c fiber.Ctx) error { 74 | if cfg.Next != nil && cfg.Next(c) { 75 | return c.Next() 76 | } 77 | 78 | txn := app.StartTransaction(createTransactionName(c)) 79 | defer txn.End() 80 | 81 | var ( 82 | host = utils.CopyString(c.Hostname()) 83 | method = utils.CopyString(c.Method()) 84 | ) 85 | 86 | scheme := c.Request().URI().Scheme() 87 | 88 | txn.SetWebRequest(newrelic.WebRequest{ 89 | Host: host, 90 | Method: method, 91 | Transport: transport(string(scheme)), 92 | URL: &url.URL{ 93 | Host: host, 94 | Scheme: string(c.Request().URI().Scheme()), 95 | Path: string(c.Request().URI().Path()), 96 | RawQuery: string(c.Request().URI().QueryString()), 97 | }, 98 | }) 99 | 100 | c.SetContext(newrelic.NewContext(c.Context(), txn)) 101 | 102 | handlerErr := c.Next() 103 | statusCode := c.RequestCtx().Response.StatusCode() 104 | 105 | if handlerErr != nil { 106 | statusCode = cfg.ErrorStatusCodeHandler(c, handlerErr) 107 | txn.NoticeError(handlerErr) 108 | } 109 | 110 | txn.SetWebResponse(nil).WriteHeader(statusCode) 111 | 112 | return handlerErr 113 | } 114 | } 115 | 116 | // FromContext returns the Transaction from the context if present, and nil 117 | // otherwise. 118 | func FromContext(c fiber.Ctx) *newrelic.Transaction { 119 | return newrelic.FromContext(c) 120 | } 121 | 122 | func createTransactionName(c fiber.Ctx) string { 123 | return fmt.Sprintf("%s %s", c.Request().Header.Method(), c.Request().URI().Path()) 124 | } 125 | 126 | func transport(schema string) newrelic.TransportType { 127 | if strings.HasPrefix(schema, "https") { 128 | return newrelic.TransportHTTPS 129 | } 130 | 131 | if strings.HasPrefix(schema, "http") { 132 | return newrelic.TransportHTTP 133 | } 134 | 135 | return newrelic.TransportUnknown 136 | } 137 | 138 | func DefaultErrorStatusCodeHandler(c fiber.Ctx, err error) int { 139 | if fiberErr, ok := err.(*fiber.Error); ok { 140 | return fiberErr.Code 141 | } 142 | 143 | return c.RequestCtx().Response.StatusCode() 144 | } 145 | -------------------------------------------------------------------------------- /fibernewrelic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fibernewrelic 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/gofiber/utils/v2 v2.0.0-rc.1 8 | github.com/newrelic/go-agent/v3 v3.33.1 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/gofiber/schema v1.6.0 // indirect 16 | github.com/golang/protobuf v1.5.3 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/tinylib/msgp v1.4.0 // indirect 24 | github.com/valyala/bytebufferpool v1.0.0 // indirect 25 | github.com/valyala/fasthttp v1.65.0 // indirect 26 | golang.org/x/crypto v0.42.0 // indirect 27 | golang.org/x/net v0.44.0 // indirect 28 | golang.org/x/sys v0.36.0 // indirect 29 | golang.org/x/text v0.29.0 // indirect 30 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 31 | google.golang.org/grpc v1.56.3 // indirect 32 | google.golang.org/protobuf v1.33.0 // indirect 33 | gopkg.in/yaml.v3 v3.0.1 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /fibersentry/config.go: -------------------------------------------------------------------------------- 1 | package fibersentry 2 | 3 | import "time" 4 | 5 | const hubKey = "sentry-hub" 6 | 7 | // Config defines the config for middleware. 8 | type Config struct { 9 | // Repanic configures whether Sentry should repanic after recovery. 10 | // Set to true, if Recover middleware is used. 11 | // https://github.com/gofiber/fiber/tree/master/middleware/recover 12 | // Optional. Default: false 13 | Repanic bool 14 | 15 | // WaitForDelivery configures whether you want to block the request before moving forward with the response. 16 | // If Recover middleware is used, it's safe to either skip this option or set it to false. 17 | // https://github.com/gofiber/fiber/tree/master/middleware/recover 18 | // Optional. Default: false 19 | WaitForDelivery bool 20 | 21 | // Timeout for the event delivery requests. 22 | // Optional. Default: 2 Seconds 23 | Timeout time.Duration 24 | } 25 | 26 | // ConfigDefault is the default config 27 | var ConfigDefault = Config{ 28 | Repanic: false, 29 | WaitForDelivery: false, 30 | Timeout: time.Second * 2, 31 | } 32 | 33 | // Helper function to set default values 34 | func configDefault(config ...Config) Config { 35 | // Return default config if nothing provided 36 | if len(config) < 1 { 37 | return ConfigDefault 38 | } 39 | 40 | // Override default config 41 | cfg := config[0] 42 | 43 | if cfg.Timeout == 0 { 44 | cfg.Timeout = time.Second * 2 45 | } 46 | 47 | return cfg 48 | } 49 | -------------------------------------------------------------------------------- /fibersentry/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fibersentry 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/getsentry/sentry-go v0.35.3 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | github.com/gofiber/utils/v2 v2.0.0-rc.1 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/gofiber/schema v1.6.0 // indirect 14 | github.com/kr/text v0.2.0 // indirect 15 | github.com/philhofer/fwd v1.2.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/tinylib/msgp v1.4.0 // indirect 18 | github.com/valyala/fasthttp v1.65.0 // indirect 19 | golang.org/x/crypto v0.42.0 // indirect 20 | golang.org/x/net v0.44.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | 24 | require ( 25 | github.com/andybalholm/brotli v1.2.0 // indirect 26 | github.com/google/uuid v1.6.0 // indirect 27 | github.com/klauspost/compress v1.18.0 // indirect 28 | github.com/mattn/go-colorable v0.1.14 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/stretchr/testify v1.11.1 31 | github.com/valyala/bytebufferpool v1.0.0 // indirect 32 | golang.org/x/sys v0.36.0 // indirect 33 | golang.org/x/text v0.29.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /fibersentry/sentry.go: -------------------------------------------------------------------------------- 1 | package fibersentry 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/getsentry/sentry-go" 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/gofiber/fiber/v3/middleware/adaptor" 9 | "github.com/gofiber/utils/v2" 10 | ) 11 | 12 | // New creates a new middleware handler 13 | func New(config ...Config) fiber.Handler { 14 | // Set default config 15 | cfg := configDefault(config...) 16 | 17 | // Return new handler 18 | return func(c fiber.Ctx) error { 19 | // Convert fiber request to http request 20 | r, err := adaptor.ConvertRequest(c, true) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | // Init sentry hub 26 | hub := sentry.CurrentHub().Clone() 27 | scope := hub.Scope() 28 | scope.SetRequest(r) 29 | scope.SetRequestBody(utils.CopyBytes(c.Body())) 30 | c.Locals(hubKey, hub) 31 | 32 | // Catch panics 33 | defer func() { 34 | if err := recover(); err != nil { 35 | eventID := hub.RecoverWithContext( 36 | context.WithValue(context.Background(), sentry.RequestContextKey, c), 37 | err, 38 | ) 39 | 40 | if eventID != nil && cfg.WaitForDelivery { 41 | hub.Flush(cfg.Timeout) 42 | } 43 | 44 | if cfg.Repanic { 45 | panic(err) 46 | } 47 | } 48 | }() 49 | 50 | // Return err if exist, else move to next handler 51 | return c.Next() 52 | } 53 | } 54 | 55 | func MustGetHubFromContext(ctx fiber.Ctx) *sentry.Hub { 56 | return ctx.Locals(hubKey).(*sentry.Hub) 57 | } 58 | 59 | func GetHubFromContext(ctx fiber.Ctx) *sentry.Hub { 60 | hub, ok := ctx.Locals(hubKey).(*sentry.Hub) 61 | if !ok { 62 | return nil 63 | } 64 | return hub 65 | } 66 | -------------------------------------------------------------------------------- /fiberzap/config.go: -------------------------------------------------------------------------------- 1 | package fiberzap 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v3" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | ) 8 | 9 | // Config defines the config for middleware. 10 | type Config struct { 11 | // Next defines a function to skip this middleware when returned true. 12 | // 13 | // Optional. Default: nil 14 | Next func(c fiber.Ctx) bool 15 | 16 | // SkipBody defines a function to skip log "body" field when returned true. 17 | // 18 | // Optional. Default: nil 19 | SkipBody func(c fiber.Ctx) bool 20 | 21 | // SkipResBody defines a function to skip log "resBody" field when returned true. 22 | // 23 | // Optional. Default: nil 24 | SkipResBody func(c fiber.Ctx) bool 25 | 26 | // GetResBody defines a function to get ResBody. 27 | // eg: when use compress middleware, resBody is unreadable. you can set GetResBody func to get readable resBody. 28 | // 29 | // Optional. Default: nil 30 | GetResBody func(c fiber.Ctx) []byte 31 | 32 | // Skip logging for these uri 33 | // 34 | // Optional. Default: nil 35 | SkipURIs []string 36 | 37 | // Add custom zap logger. 38 | // 39 | // Optional. Default: zap.NewProduction() 40 | Logger *zap.Logger 41 | 42 | // Add fields what you want see. 43 | // 44 | // Optional. Default: {"ip", "latency", "status", "method", "url"} 45 | Fields []string 46 | 47 | // FieldsFunc defines a function to return custom zap fields to append to the log. 48 | // 49 | // Optional. Default: nil 50 | FieldsFunc func(c fiber.Ctx) []zap.Field 51 | 52 | // Custom response messages. 53 | // Response codes >= 500 will be logged with Messages[0]. 54 | // Response codes >= 400 will be logged with Messages[1]. 55 | // Other response codes will be logged with Messages[2]. 56 | // You can specify less, than 3 messages, but you must specify at least 1. 57 | // Specifying more than 3 messages is useless. 58 | // 59 | // Optional. Default: {"Server error", "Client error", "Success"} 60 | Messages []string 61 | 62 | // Custom response levels. 63 | // Response codes >= 500 will be logged with Levels[0]. 64 | // Response codes >= 400 will be logged with Levels[1]. 65 | // Other response codes will be logged with Levels[2]. 66 | // You can specify less, than 3 levels, but you must specify at least 1. 67 | // Specifying more than 3 levels is useless. 68 | // 69 | // Optional. Default: {zapcore.ErrorLevel, zapcore.WarnLevel, zapcore.InfoLevel} 70 | Levels []zapcore.Level 71 | } 72 | 73 | // Use zap.NewProduction() as default logging instance. 74 | var logger, _ = zap.NewProduction() 75 | 76 | // ConfigDefault is the default config 77 | var ConfigDefault = Config{ 78 | Next: nil, 79 | Logger: logger, 80 | Fields: []string{"ip", "latency", "status", "method", "url"}, 81 | FieldsFunc: nil, 82 | Messages: []string{"Server error", "Client error", "Success"}, 83 | Levels: []zapcore.Level{zapcore.ErrorLevel, zapcore.WarnLevel, zapcore.InfoLevel}, 84 | } 85 | 86 | // Helper function to set default values 87 | func configDefault(config ...Config) Config { 88 | // Return default config if nothing provided 89 | if len(config) < 1 { 90 | return ConfigDefault 91 | } 92 | 93 | // Override default config 94 | cfg := config[0] 95 | 96 | // Set default values 97 | if cfg.Next == nil { 98 | cfg.Next = ConfigDefault.Next 99 | } 100 | 101 | if cfg.Logger == nil { 102 | cfg.Logger = ConfigDefault.Logger 103 | } 104 | 105 | if cfg.Fields == nil { 106 | cfg.Fields = ConfigDefault.Fields 107 | } 108 | 109 | if cfg.Messages == nil { 110 | cfg.Messages = ConfigDefault.Messages 111 | } 112 | 113 | if cfg.Levels == nil { 114 | cfg.Levels = ConfigDefault.Levels 115 | } 116 | 117 | if cfg.FieldsFunc == nil { 118 | cfg.FieldsFunc = ConfigDefault.FieldsFunc 119 | } 120 | 121 | return cfg 122 | } 123 | -------------------------------------------------------------------------------- /fiberzap/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fiberzap/v2 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/stretchr/testify v1.11.1 8 | github.com/valyala/fasthttp v1.65.0 9 | go.uber.org/zap v1.27.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/gofiber/schema v1.6.0 // indirect 16 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/tinylib/msgp v1.4.0 // indirect 24 | github.com/valyala/bytebufferpool v1.0.0 // indirect 25 | go.uber.org/multierr v1.10.0 // indirect 26 | golang.org/x/crypto v0.42.0 // indirect 27 | golang.org/x/net v0.44.0 // indirect 28 | golang.org/x/sys v0.36.0 // indirect 29 | golang.org/x/text v0.29.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /fiberzap/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 2 | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 6 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0= 8 | github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg= 9 | github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= 10 | github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= 11 | github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s= 12 | github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E= 13 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 14 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 15 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 16 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 17 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 18 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 19 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 | github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= 22 | github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo= 26 | github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= 27 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 28 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 29 | github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= 30 | github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= 31 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 32 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 33 | github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8= 34 | github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4= 35 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 36 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 37 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 38 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 39 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 40 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 41 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 42 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 43 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 44 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 45 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 46 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 47 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 48 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 49 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 51 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 52 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 53 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 57 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 58 | -------------------------------------------------------------------------------- /fiberzerolog/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/fiberzerolog 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/rs/zerolog v1.34.0 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.2.0 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/gofiber/schema v1.6.0 // indirect 15 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 16 | github.com/google/uuid v1.6.0 // indirect 17 | github.com/klauspost/compress v1.18.0 // indirect 18 | github.com/mattn/go-colorable v0.1.14 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/philhofer/fwd v1.2.0 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | github.com/tinylib/msgp v1.4.0 // indirect 23 | github.com/valyala/bytebufferpool v1.0.0 // indirect 24 | github.com/valyala/fasthttp v1.65.0 // indirect 25 | golang.org/x/crypto v0.42.0 // indirect 26 | golang.org/x/net v0.44.0 // indirect 27 | golang.org/x/sys v0.36.0 // indirect 28 | golang.org/x/text v0.29.0 // indirect 29 | gopkg.in/yaml.v3 v3.0.1 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /fiberzerolog/zerolog.go: -------------------------------------------------------------------------------- 1 | package fiberzerolog 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gofiber/fiber/v3" 7 | "github.com/rs/zerolog" 8 | ) 9 | 10 | // New creates a new middleware handler 11 | func New(config ...Config) fiber.Handler { 12 | // Set default config 13 | cfg := configDefault(config...) 14 | 15 | // put ignore uri into a map for faster match 16 | skipURIs := make(map[string]struct{}, len(cfg.SkipURIs)) 17 | for _, uri := range cfg.SkipURIs { 18 | skipURIs[uri] = struct{}{} 19 | } 20 | 21 | // Return new handler 22 | return func(c fiber.Ctx) error { 23 | // Don't execute middleware if Next returns true 24 | if cfg.Next != nil && cfg.Next(c) { 25 | return c.Next() 26 | } 27 | 28 | // skip uri 29 | if _, ok := skipURIs[c.Path()]; ok { 30 | return c.Next() 31 | } 32 | 33 | start := time.Now() 34 | 35 | // Handle request, store err for logging 36 | chainErr := c.Next() 37 | if chainErr != nil { 38 | // Manually call error handler 39 | if err := c.App().ErrorHandler(c, chainErr); err != nil { 40 | _ = c.SendStatus(fiber.StatusInternalServerError) 41 | } 42 | } 43 | 44 | latency := time.Since(start) 45 | 46 | status := c.Response().StatusCode() 47 | 48 | index := 0 49 | switch { 50 | case status >= 500: 51 | // error index is zero 52 | case status >= 400: 53 | index = 1 54 | default: 55 | index = 2 56 | } 57 | 58 | levelIndex := index 59 | if levelIndex >= len(cfg.Levels) { 60 | levelIndex = len(cfg.Levels) - 1 61 | } 62 | level := cfg.Levels[levelIndex] 63 | 64 | // no log 65 | if level == zerolog.NoLevel || level == zerolog.Disabled { 66 | return nil 67 | } 68 | 69 | messageIndex := index 70 | if messageIndex >= len(cfg.Messages) { 71 | messageIndex = len(cfg.Messages) - 1 72 | } 73 | message := cfg.Messages[messageIndex] 74 | 75 | logger := cfg.logger(c, latency, chainErr) 76 | ctx := c 77 | 78 | switch level { 79 | case zerolog.DebugLevel: 80 | logger.Debug().Ctx(ctx).Msg(message) 81 | case zerolog.InfoLevel: 82 | logger.Info().Ctx(ctx).Msg(message) 83 | case zerolog.WarnLevel: 84 | logger.Warn().Ctx(ctx).Msg(message) 85 | case zerolog.ErrorLevel: 86 | logger.Error().Ctx(ctx).Msg(message) 87 | case zerolog.FatalLevel: 88 | logger.Fatal().Ctx(ctx).Msg(message) 89 | case zerolog.PanicLevel: 90 | logger.Panic().Ctx(ctx).Msg(message) 91 | case zerolog.TraceLevel: 92 | logger.Trace().Ctx(ctx).Msg(message) 93 | } 94 | 95 | return nil 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.25.0 2 | 3 | use ( 4 | ./casbin 5 | ./circuitbreaker 6 | ./fgprof 7 | ./fiberi18n 8 | ./fibernewrelic 9 | ./fibersentry 10 | ./fiberzap 11 | ./fiberzerolog 12 | ./hcaptcha 13 | ./jwt 14 | ./loadshed 15 | ./monitor 16 | ./opafiber 17 | ./otelfiber 18 | ./paseto 19 | ./socketio 20 | ./swagger 21 | ./testcontainers 22 | ./websocket 23 | ) 24 | -------------------------------------------------------------------------------- /hcaptcha/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: hcaptcha 3 | --- 4 | 5 | # HCaptcha 6 | 7 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=hcaptcha*) 8 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 9 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20hcaptcha/badge.svg) 10 | 11 | A simple [HCaptcha](https://hcaptcha.com) middleware to prevent bot attacks. 12 | 13 | :::note 14 | 15 | Requires Go **1.25** and above 16 | 17 | ::: 18 | 19 | ## Install 20 | 21 | :::caution 22 | 23 | This middleware only supports Fiber **v3**. 24 | 25 | ::: 26 | 27 | ```shell 28 | go get -u github.com/gofiber/fiber/v3 29 | go get -u github.com/gofiber/contrib/hcaptcha 30 | ``` 31 | 32 | ## Signature 33 | 34 | ```go 35 | hcaptcha.New(config hcaptcha.Config) fiber.Handler 36 | ``` 37 | 38 | ## Config 39 | 40 | | Property | Type | Description | Default | 41 | |:----------------|:----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------| 42 | | SecretKey | `string` | The secret key you obtained from the HCaptcha admin panel. This field must not be empty. | `""` | 43 | | ResponseKeyFunc | `func(fiber.Ctx) (string, error)` | ResponseKeyFunc should return the token that captcha provides upon successful solving. By default, it gets the token from the body by parsing a JSON request and returns the `hcaptcha_token` field. | `hcaptcha.DefaultResponseKeyFunc` | 44 | | SiteVerifyURL | `string` | This property specifies the API resource used for token authentication. | `https://api.hcaptcha.com/siteverify` | 45 | 46 | ## Example 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | "github.com/gofiber/contrib/hcaptcha" 53 | "github.com/gofiber/fiber/v3" 54 | "log" 55 | ) 56 | 57 | const ( 58 | TestSecretKey = "0x0000000000000000000000000000000000000000" 59 | TestSiteKey = "20000000-ffff-ffff-ffff-000000000002" 60 | ) 61 | 62 | func main() { 63 | app := fiber.New() 64 | captcha := hcaptcha.New(hcaptcha.Config{ 65 | // Must set the secret key 66 | SecretKey: TestSecretKey, 67 | }) 68 | 69 | app.Get("/api/", func(c fiber.Ctx) error { 70 | return c.JSON(fiber.Map{ 71 | "hcaptcha_site_key": TestSiteKey, 72 | }) 73 | }) 74 | 75 | app.Post("/api/robots-excluded", func(c fiber.Ctx) error { 76 | return c.SendString("You are not a robot") 77 | }, captcha) 78 | 79 | log.Fatal(app.Listen(":3000")) 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /hcaptcha/config.go: -------------------------------------------------------------------------------- 1 | package hcaptcha 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gofiber/fiber/v3" 8 | ) 9 | 10 | // DefaultSiteVerifyURL is the default URL for the HCaptcha API 11 | const DefaultSiteVerifyURL = "https://api.hcaptcha.com/siteverify" 12 | 13 | // Config defines the config for HCaptcha middleware. 14 | type Config struct { 15 | // SecretKey is the secret key you get from HCaptcha when you create a new application 16 | SecretKey string 17 | // ResponseKeyFunc should return the generated pass UUID from the ctx, which will be validated 18 | ResponseKeyFunc func(fiber.Ctx) (string, error) 19 | // SiteVerifyURL is the endpoint URL where the program should verify the given token 20 | // default value is: "https://api.hcaptcha.com/siteverify" 21 | SiteVerifyURL string 22 | } 23 | 24 | // DefaultResponseKeyFunc is the default function to get the HCaptcha token from the request body 25 | func DefaultResponseKeyFunc(c fiber.Ctx) (string, error) { 26 | data := struct { 27 | HCaptchaToken string `json:"hcaptcha_token"` 28 | }{} 29 | 30 | err := json.NewDecoder(bytes.NewReader(c.Body())).Decode(&data) 31 | 32 | if err != nil { 33 | return "", fmt.Errorf("failed to decode HCaptcha token: %w", err) 34 | } 35 | 36 | return data.HCaptchaToken, nil 37 | } 38 | -------------------------------------------------------------------------------- /hcaptcha/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/hcaptcha 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/stretchr/testify v1.11.1 8 | github.com/valyala/fasthttp v1.65.0 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.2.0 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/gofiber/schema v1.6.0 // indirect 15 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 16 | github.com/google/uuid v1.6.0 // indirect 17 | github.com/klauspost/compress v1.18.0 // indirect 18 | github.com/mattn/go-colorable v0.1.14 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/philhofer/fwd v1.2.0 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | github.com/tinylib/msgp v1.4.0 // indirect 23 | github.com/valyala/bytebufferpool v1.0.0 // indirect 24 | golang.org/x/crypto v0.42.0 // indirect 25 | golang.org/x/net v0.44.0 // indirect 26 | golang.org/x/sys v0.36.0 // indirect 27 | golang.org/x/text v0.29.0 // indirect 28 | gopkg.in/yaml.v3 v3.0.1 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /hcaptcha/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 2 | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 6 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0= 8 | github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg= 9 | github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= 10 | github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= 11 | github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s= 12 | github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E= 13 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 14 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 15 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 16 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 17 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 18 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 19 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 | github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= 22 | github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo= 26 | github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= 27 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 28 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 29 | github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= 30 | github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= 31 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 32 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 33 | github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8= 34 | github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4= 35 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 36 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 37 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 38 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 39 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 40 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 41 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 42 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 45 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 46 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 47 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 51 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | -------------------------------------------------------------------------------- /hcaptcha/hcaptcha.go: -------------------------------------------------------------------------------- 1 | // Package hcaptcha is a simple middleware that checks for an HCaptcha UUID 2 | // and then validates it. It returns an error if the UUID is not valid (the request may have been sent by a robot). 3 | package hcaptcha 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "github.com/gofiber/fiber/v3" 11 | "github.com/valyala/fasthttp" 12 | "net/url" 13 | ) 14 | 15 | // HCaptcha is a middleware handler that checks for an HCaptcha UUID and then validates it. 16 | type HCaptcha struct { 17 | Config 18 | } 19 | 20 | // New creates a new HCaptcha middleware handler. 21 | func New(config Config) fiber.Handler { 22 | if config.SiteVerifyURL == "" { 23 | config.SiteVerifyURL = DefaultSiteVerifyURL 24 | } 25 | 26 | if config.ResponseKeyFunc == nil { 27 | config.ResponseKeyFunc = DefaultResponseKeyFunc 28 | } 29 | 30 | h := &HCaptcha{ 31 | config, 32 | } 33 | return h.Validate 34 | } 35 | 36 | // Validate checks for an HCaptcha UUID and then validates it. 37 | func (h *HCaptcha) Validate(c fiber.Ctx) error { 38 | token, err := h.ResponseKeyFunc(c) 39 | if err != nil { 40 | c.Status(fiber.StatusBadRequest) 41 | return fmt.Errorf("error retrieving HCaptcha token: %w", err) 42 | } 43 | 44 | req := fasthttp.AcquireRequest() 45 | defer fasthttp.ReleaseRequest(req) 46 | req.SetBody([]byte(url.Values{ 47 | "secret": {h.SecretKey}, 48 | "response": {token}, 49 | }.Encode())) 50 | req.Header.SetMethod("POST") 51 | req.Header.SetContentType("application/x-www-form-urlencoded; charset=UTF-8") 52 | req.Header.Set("Accept", "application/json") 53 | req.SetRequestURI(h.SiteVerifyURL) 54 | res := fasthttp.AcquireResponse() 55 | defer fasthttp.ReleaseResponse(res) 56 | 57 | // Send the request to the HCaptcha API 58 | if err = fasthttp.Do(req, res); err != nil { 59 | c.Status(fiber.StatusBadRequest) 60 | return fmt.Errorf("error sending request to HCaptcha API: %w", err) 61 | } 62 | 63 | o := struct { 64 | Success bool `json:"success"` 65 | }{} 66 | 67 | if err = json.NewDecoder(bytes.NewReader(res.Body())).Decode(&o); err != nil { 68 | c.Status(fiber.StatusInternalServerError) 69 | return fmt.Errorf("error decoding HCaptcha API response: %w", err) 70 | } 71 | 72 | if !o.Success { 73 | c.Status(fiber.StatusForbidden) 74 | return errors.New("unable to check that you are not a robot") 75 | } 76 | 77 | return c.Next() 78 | } 79 | -------------------------------------------------------------------------------- /hcaptcha/hcaptcha_test.go: -------------------------------------------------------------------------------- 1 | package hcaptcha 2 | 3 | import ( 4 | "io" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/gofiber/fiber/v3" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const ( 13 | TestSecretKey = "0x0000000000000000000000000000000000000000" 14 | TestResponseToken = "20000000-aaaa-bbbb-cccc-000000000002" // Got by using this site key: 20000000-ffff-ffff-ffff-000000000002 15 | ) 16 | 17 | // TestHCaptcha tests the hcaptcha middleware 18 | func TestHCaptcha(t *testing.T) { 19 | app := fiber.New() 20 | 21 | m := New(Config{ 22 | SecretKey: TestSecretKey, 23 | ResponseKeyFunc: func(c fiber.Ctx) (string, error) { 24 | return c.Query("token"), nil 25 | }, 26 | }) 27 | 28 | app.Get("/hcaptcha", func(c fiber.Ctx) error { 29 | return c.Status(200).SendString("ok") 30 | }, m) 31 | 32 | req := httptest.NewRequest("GET", "/hcaptcha?token="+TestResponseToken, nil) 33 | req.Header.Set("Content-Type", "application/json") 34 | 35 | res, err := app.Test(req, fiber.TestConfig{Timeout: 0, FailOnTimeout: false}) 36 | defer res.Body.Close() 37 | 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | assert.Equal(t, res.StatusCode, fiber.StatusOK, "Response status code") 43 | 44 | body, err := io.ReadAll(res.Body) 45 | 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | assert.Equal(t, "ok", string(body)) 51 | } 52 | -------------------------------------------------------------------------------- /jwt/config_test.go: -------------------------------------------------------------------------------- 1 | package jwtware 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/gofiber/fiber/v3/extractors" 9 | "github.com/golang-jwt/jwt/v5" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestPanicOnMissingConfiguration(t *testing.T) { 14 | t.Parallel() 15 | 16 | defer func() { 17 | // Assert 18 | if err := recover(); err == nil { 19 | t.Fatalf("Middleware should panic on missing configuration") 20 | } 21 | }() 22 | 23 | // Arrange 24 | config := make([]Config, 0) 25 | 26 | // Act 27 | makeCfg(config) 28 | } 29 | 30 | func TestDefaultConfiguration(t *testing.T) { 31 | t.Parallel() 32 | 33 | // Arrange 34 | config := append(make([]Config, 0), Config{ 35 | SigningKey: SigningKey{Key: []byte("")}, 36 | }) 37 | 38 | // Act 39 | cfg := makeCfg(config) 40 | 41 | // Assert 42 | require.NotNil(t, cfg.Claims, "Default claims should not be 'nil'") 43 | require.Equal(t, extractors.SourceAuthHeader, cfg.Extractor.Source, "Default extractor source should be '%v'", extractors.SourceAuthHeader) 44 | require.Equal(t, fiber.HeaderAuthorization, cfg.Extractor.Key, "Default extractor key should be '%v'", fiber.HeaderAuthorization) 45 | require.Equal(t, "Bearer", cfg.Extractor.AuthScheme, "Default auth scheme should be 'Bearer'") 46 | } 47 | 48 | func TestCustomExtractor(t *testing.T) { 49 | t.Parallel() 50 | 51 | // Arrange 52 | extractor := extractors.FromHeader("X-Auth-Token") 53 | config := append(make([]Config, 0), Config{ 54 | SigningKey: SigningKey{Key: []byte("")}, 55 | Extractor: extractor, 56 | }) 57 | 58 | // Act 59 | cfg := makeCfg(config) 60 | 61 | // Assert 62 | require.Equal(t, extractor.Source, cfg.Extractor.Source, "Extractor source should be the custom one") 63 | require.Equal(t, extractor.Key, cfg.Extractor.Key, "Extractor key should be the custom one") 64 | require.Equal(t, "", cfg.Extractor.AuthScheme, "AuthScheme should be empty for non-Authorization extractors") 65 | } 66 | 67 | func TestPanicOnInvalidSigningKey(t *testing.T) { 68 | t.Parallel() 69 | config := append(make([]Config, 0), Config{ 70 | SigningKey: SigningKey{Key: nil}, // Invalid key 71 | }) 72 | require.Panics(t, func() { makeCfg(config) }) 73 | } 74 | 75 | func TestPanicOnInvalidSigningKeys(t *testing.T) { 76 | t.Parallel() 77 | config := append(make([]Config, 0), Config{ 78 | SigningKeys: map[string]SigningKey{ 79 | "key1": {Key: nil}, // Invalid key 80 | }, 81 | }) 82 | require.Panics(t, func() { makeCfg(config) }) 83 | } 84 | 85 | func TestPanicOnInvalidJWKSetURLs(t *testing.T) { 86 | t.Parallel() 87 | // Arrange 88 | config := append(make([]Config, 0), Config{ 89 | JWKSetURLs: []string{"invalid-url"}, // This would cause panic in keyfunc 90 | }) 91 | require.Panics(t, func() { makeCfg(config) }) 92 | } 93 | 94 | func TestCustomClaims(t *testing.T) { 95 | t.Parallel() 96 | 97 | // Arrange 98 | customClaims := jwt.MapClaims{"custom": "claims"} 99 | config := append(make([]Config, 0), Config{ 100 | SigningKey: SigningKey{Key: []byte("")}, 101 | Claims: customClaims, 102 | }) 103 | 104 | // Act 105 | cfg := makeCfg(config) 106 | 107 | // Assert 108 | require.NotNil(t, cfg.Claims, "Custom claims should be preserved") 109 | 110 | // Check if it's the same map by checking a key 111 | claimsMap, ok := cfg.Claims.(jwt.MapClaims) 112 | require.True(t, ok, "Claims should be MapClaims") 113 | require.Equal(t, "claims", claimsMap["custom"], "Custom claims content should be preserved") 114 | } 115 | 116 | func TestTokenProcessorFunc_Configured(t *testing.T) { 117 | t.Parallel() 118 | 119 | // Arrange 120 | config := append(make([]Config, 0), Config{ 121 | SigningKey: SigningKey{Key: []byte("")}, 122 | TokenProcessorFunc: func(token string) (string, error) { 123 | return "", fmt.Errorf("processing failed") 124 | }, 125 | }) 126 | 127 | // Act 128 | cfg := makeCfg(config) 129 | 130 | // Assert 131 | require.NotNil(t, cfg.TokenProcessorFunc, "TokenProcessorFunc should be set") 132 | 133 | // Exercise the processor 134 | _, err := cfg.TokenProcessorFunc("dummy") 135 | require.Error(t, err, "TokenProcessorFunc should return error") 136 | } 137 | 138 | func TestPanicOnUnsupportedJWKSetURLScheme(t *testing.T) { 139 | t.Parallel() 140 | config := append(make([]Config, 0), Config{ 141 | JWKSetURLs: []string{"ftp://example.com"}, // Unsupported scheme 142 | }) 143 | require.Panics(t, func() { makeCfg(config) }) 144 | } 145 | -------------------------------------------------------------------------------- /jwt/crypto.go: -------------------------------------------------------------------------------- 1 | package jwtware 2 | 3 | const ( 4 | // HS256 represents a public cryptography key generated by a 256 bit HMAC algorithm. 5 | HS256 = "HS256" 6 | 7 | // HS384 represents a public cryptography key generated by a 384 bit HMAC algorithm. 8 | HS384 = "HS384" 9 | 10 | // HS512 represents a public cryptography key generated by a 512 bit HMAC algorithm. 11 | HS512 = "HS512" 12 | 13 | // ES256 represents a public cryptography key generated by a 256 bit ECDSA algorithm. 14 | ES256 = "ES256" 15 | 16 | // ES384 represents a public cryptography key generated by a 384 bit ECDSA algorithm. 17 | ES384 = "ES384" 18 | 19 | // ES512 represents a public cryptography key generated by a 512 bit ECDSA algorithm. 20 | ES512 = "ES512" 21 | 22 | // P256 represents a cryptographic elliptical curve type. 23 | P256 = "P-256" 24 | 25 | // P384 represents a cryptographic elliptical curve type. 26 | P384 = "P-384" 27 | 28 | // P521 represents a cryptographic elliptical curve type. 29 | P521 = "P-521" 30 | 31 | // RS256 represents a public cryptography key generated by a 256 bit RSA algorithm. 32 | RS256 = "RS256" 33 | 34 | // RS384 represents a public cryptography key generated by a 384 bit RSA algorithm. 35 | RS384 = "RS384" 36 | 37 | // RS512 represents a public cryptography key generated by a 512 bit RSA algorithm. 38 | RS512 = "RS512" 39 | 40 | // PS256 represents a public cryptography key generated by a 256 bit RSA algorithm. 41 | PS256 = "PS256" 42 | 43 | // PS384 represents a public cryptography key generated by a 384 bit RSA algorithm. 44 | PS384 = "PS384" 45 | 46 | // PS512 represents a public cryptography key generated by a 512 bit RSA algorithm. 47 | PS512 = "PS512" 48 | ) 49 | -------------------------------------------------------------------------------- /jwt/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/jwt 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/MicahParks/keyfunc/v2 v2.1.0 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | github.com/golang-jwt/jwt/v5 v5.3.0 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/gofiber/schema v1.6.0 // indirect 16 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/tinylib/msgp v1.4.0 // indirect 24 | github.com/valyala/bytebufferpool v1.0.0 // indirect 25 | github.com/valyala/fasthttp v1.66.0 // indirect 26 | golang.org/x/crypto v0.42.0 // indirect 27 | golang.org/x/net v0.44.0 // indirect 28 | golang.org/x/sys v0.36.0 // indirect 29 | golang.org/x/text v0.29.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /jwt/go.sum: -------------------------------------------------------------------------------- 1 | github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= 2 | github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= 3 | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 4 | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 8 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 9 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0= 10 | github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg= 11 | github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= 12 | github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= 13 | github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s= 14 | github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E= 15 | github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= 16 | github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 17 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 18 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 19 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 20 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 21 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 22 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 23 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 24 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 25 | github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= 26 | github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo= 30 | github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= 31 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 32 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 33 | github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= 34 | github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= 35 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 36 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 37 | github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU= 38 | github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= 39 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 40 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 41 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 42 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 43 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 44 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 45 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 46 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 47 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 49 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 50 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 51 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 55 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 56 | -------------------------------------------------------------------------------- /jwt/jwt.go: -------------------------------------------------------------------------------- 1 | // 🚀 Fiber is an Express inspired web framework written in Go with 💖 2 | // 📌 API Documentation: https://fiber.wiki 3 | // 📝 Github Repository: https://github.com/gofiber/fiber 4 | // Special thanks to Echo: https://github.com/labstack/echo/blob/master/middleware/jwt.go 5 | 6 | package jwtware 7 | 8 | import ( 9 | "reflect" 10 | 11 | "github.com/gofiber/fiber/v3" 12 | "github.com/golang-jwt/jwt/v5" 13 | ) 14 | 15 | // The contextKey type is unexported to prevent collisions with context keys defined in 16 | // other packages. 17 | type contextKey int 18 | 19 | // The following contextKey values are defined to store values in context. 20 | const ( 21 | tokenKey contextKey = iota 22 | ) 23 | 24 | // New ... 25 | func New(config ...Config) fiber.Handler { 26 | cfg := makeCfg(config) 27 | 28 | // Return middleware handler 29 | return func(c fiber.Ctx) error { 30 | // Filter request to skip middleware 31 | if cfg.Next != nil && cfg.Next(c) { 32 | return c.Next() 33 | } 34 | auth, err := cfg.Extractor.Extract(c) 35 | if err != nil { 36 | return cfg.ErrorHandler(c, err) 37 | } 38 | 39 | if cfg.TokenProcessorFunc != nil { 40 | auth, err = cfg.TokenProcessorFunc(auth) 41 | if err != nil { 42 | return cfg.ErrorHandler(c, err) 43 | } 44 | } 45 | 46 | var token *jwt.Token 47 | if _, ok := cfg.Claims.(jwt.MapClaims); ok { 48 | token, err = jwt.Parse(auth, cfg.KeyFunc) 49 | } else { 50 | t := reflect.ValueOf(cfg.Claims).Type().Elem() 51 | claims := reflect.New(t).Interface().(jwt.Claims) 52 | token, err = jwt.ParseWithClaims(auth, claims, cfg.KeyFunc) 53 | } 54 | if err == nil && token.Valid { 55 | // Store user information from token into context. 56 | c.Locals(tokenKey, token) 57 | return cfg.SuccessHandler(c) 58 | } 59 | return cfg.ErrorHandler(c, err) 60 | } 61 | } 62 | 63 | // FromContext returns the token from the context. 64 | // If there is no token, nil is returned. 65 | func FromContext(c fiber.Ctx) *jwt.Token { 66 | token, ok := c.Locals(tokenKey).(*jwt.Token) 67 | if !ok { 68 | return nil 69 | } 70 | return token 71 | } 72 | -------------------------------------------------------------------------------- /loadshed/cpu.go: -------------------------------------------------------------------------------- 1 | package loadshed 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/shirou/gopsutil/cpu" 9 | ) 10 | 11 | // LoadCriteria interface for different types of load metrics. 12 | type LoadCriteria interface { 13 | Metric(ctx context.Context) (float64, error) 14 | ShouldShed(metric float64) bool 15 | } 16 | 17 | // CPULoadCriteria for using CPU as a load metric. 18 | type CPULoadCriteria struct { 19 | LowerThreshold float64 20 | UpperThreshold float64 21 | Interval time.Duration 22 | Getter CPUPercentGetter 23 | } 24 | 25 | func (c *CPULoadCriteria) Metric(ctx context.Context) (float64, error) { 26 | percentages, err := c.Getter.PercentWithContext(ctx, c.Interval, false) 27 | if err != nil || len(percentages) == 0 { 28 | return 0, err 29 | } 30 | return percentages[0], nil 31 | } 32 | 33 | func (c *CPULoadCriteria) ShouldShed(metric float64) bool { 34 | if metric > c.UpperThreshold*100 { 35 | return true 36 | } else if metric > c.LowerThreshold*100 { 37 | rejectionProbability := (metric - c.LowerThreshold*100) / (c.UpperThreshold - c.LowerThreshold) 38 | // #nosec G404 39 | return rand.Float64()*100 < rejectionProbability 40 | } 41 | return false 42 | } 43 | 44 | type CPUPercentGetter interface { 45 | PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) 46 | } 47 | 48 | type DefaultCPUPercentGetter struct{} 49 | 50 | func (_ *DefaultCPUPercentGetter) PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) { 51 | return cpu.PercentWithContext(ctx, interval, percpu) 52 | } 53 | -------------------------------------------------------------------------------- /loadshed/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/loadshed 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/shirou/gopsutil v3.21.11+incompatible 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.2.0 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/go-ole/go-ole v1.2.6 // indirect 15 | github.com/gofiber/schema v1.6.0 // indirect 16 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/tinylib/msgp v1.4.0 // indirect 24 | github.com/tklauser/go-sysconf v0.3.13 // indirect 25 | github.com/tklauser/numcpus v0.7.0 // indirect 26 | github.com/valyala/bytebufferpool v1.0.0 // indirect 27 | github.com/valyala/fasthttp v1.65.0 // indirect 28 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 29 | golang.org/x/crypto v0.42.0 // indirect 30 | golang.org/x/net v0.44.0 // indirect 31 | golang.org/x/sys v0.36.0 // indirect 32 | golang.org/x/text v0.29.0 // indirect 33 | gopkg.in/yaml.v3 v3.0.1 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /loadshed/loadshed.go: -------------------------------------------------------------------------------- 1 | package loadshed 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gofiber/fiber/v3" 7 | ) 8 | 9 | type Config struct { 10 | // Function to skip this middleware when returned true. 11 | Next func(c fiber.Ctx) bool 12 | 13 | // Criteria defines the criteria to be used for load shedding. 14 | Criteria LoadCriteria 15 | 16 | // OnShed defines a custom handler that will be executed if a request should 17 | // be rejected. 18 | // 19 | // Returning `nil` without writing to the response context allows the 20 | // request to proceed to the next handler 21 | OnShed func(c fiber.Ctx) error 22 | } 23 | 24 | var ConfigDefault = Config{ 25 | Next: nil, 26 | Criteria: &CPULoadCriteria{ 27 | LowerThreshold: 0.90, 28 | UpperThreshold: 0.95, 29 | Interval: 10 * time.Second, // Evaluate the average CPU usage over the last 10 seconds. 30 | Getter: &DefaultCPUPercentGetter{}, 31 | }, 32 | } 33 | 34 | func New(config ...Config) fiber.Handler { 35 | cfg := ConfigDefault 36 | 37 | if len(config) > 0 { 38 | cfg = config[0] 39 | } 40 | 41 | return func(c fiber.Ctx) error { 42 | // Don't execute middleware if Next returns true 43 | if cfg.Next != nil && cfg.Next(c) { 44 | return c.Next() 45 | } 46 | 47 | // Compute the load metric using the specified criteria 48 | metric, err := cfg.Criteria.Metric(c.RequestCtx()) 49 | if err != nil { 50 | return c.Next() // If unable to get metric, allow the request 51 | } 52 | 53 | // Shed load if the criteria's ShouldShed method returns true 54 | if cfg.Criteria.ShouldShed(metric) { 55 | // Call the custom OnShed function 56 | if cfg.OnShed != nil { 57 | return cfg.OnShed(c) 58 | } 59 | 60 | return fiber.NewError(fiber.StatusServiceUnavailable) 61 | } 62 | 63 | return c.Next() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /monitor/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: monitor 3 | --- 4 | 5 | # Monitor 6 | 7 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=monitor*) 8 | ![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7) 9 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20Monitor/badge.svg) 10 | 11 | Monitor middleware for [Fiber](https://github.com/gofiber/fiber) that reports server metrics, inspired by [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor) 12 | 13 | ![](https://i.imgur.com/nHAtBpJ.gif) 14 | 15 | ## Install 16 | 17 | This middleware supports Fiber v3. 18 | 19 | ``` 20 | go get -u github.com/gofiber/fiber/v3 21 | go get -u github.com/gofiber/contrib/monitor 22 | ``` 23 | 24 | ### Signature 25 | 26 | ```go 27 | monitor.New(config ...monitor.Config) fiber.Handler 28 | ``` 29 | 30 | ### Config 31 | 32 | | Property | Type | Description | Default | 33 | | :--------- | :------------------------ | :----------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | 34 | | Title | `string` | Metrics page title. | `Fiber Monitor` | 35 | | Refresh | `time.Duration` | Refresh period. | `3 seconds` | 36 | | APIOnly | `bool` | Whether the service should expose only the montioring API. | `false` | 37 | | Next | `func(c *fiber.Ctx) bool` | Define a function to add custom fields. | `nil` | 38 | | CustomHead | `string` | Custom HTML code to Head Section(Before End). | `empty` | 39 | | FontURL | `string` | FontURL for specilt font resource path or URL. also you can use relative path. | `https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap` | 40 | | ChartJsURL | `string` | ChartJsURL for specilt chartjs library, path or URL, also you can use relative path. | `https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Chart.bundle.min.js` | 41 | 42 | ### Example 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "log" 49 | 50 | "github.com/gofiber/fiber/v3" 51 | "github.com/gofiber/contrib/monitor" 52 | ) 53 | 54 | func main() { 55 | app := fiber.New() 56 | 57 | // Initialize default config (Assign the middleware to /metrics) 58 | app.Get("/metrics", monitor.New()) 59 | 60 | // Or extend your config for customization 61 | // Assign the middleware to /metrics 62 | // and change the Title to `MyService Metrics Page` 63 | app.Get("/metrics", monitor.New(monitor.Config{Title: "MyService Metrics Page"})) 64 | 65 | log.Fatal(app.Listen(":3000")) 66 | } 67 | ``` 68 | 69 | 70 | ## Default Config 71 | 72 | ```go 73 | var ConfigDefault = Config{ 74 | Title: defaultTitle, 75 | Refresh: defaultRefresh, 76 | FontURL: defaultFontURL, 77 | ChartJsURL: defaultChartJSURL, 78 | CustomHead: defaultCustomHead, 79 | APIOnly: false, 80 | Next: nil, 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /monitor/config.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gofiber/fiber/v3" 7 | ) 8 | 9 | // Config defines the config for middleware. 10 | type Config struct { 11 | // Metrics page title 12 | // 13 | // Optional. Default: "Fiber Monitor" 14 | Title string 15 | 16 | // Refresh period 17 | // 18 | // Optional. Default: 3 seconds 19 | Refresh time.Duration 20 | 21 | // Whether the service should expose only the monitoring API. 22 | // 23 | // Optional. Default: false 24 | APIOnly bool 25 | 26 | // Next defines a function to skip this middleware when returned true. 27 | // 28 | // Optional. Default: nil 29 | Next func(c fiber.Ctx) bool 30 | 31 | // Custom HTML Code to Head Section(Before End) 32 | // 33 | // Optional. Default: empty 34 | CustomHead string 35 | 36 | // FontURL to specify font resource path or URL. You can also use a relative path. 37 | // 38 | // Optional. Default: https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap 39 | FontURL string 40 | 41 | // ChartJSURL to specify ChartJS library path or URL. You can also use a relative path. 42 | // 43 | // Optional. Default: https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Chart.bundle.min.js 44 | ChartJSURL string 45 | 46 | index string 47 | } 48 | 49 | var ConfigDefault = Config{ 50 | Title: defaultTitle, 51 | Refresh: defaultRefresh, 52 | FontURL: defaultFontURL, 53 | ChartJSURL: defaultChartJSURL, 54 | CustomHead: defaultCustomHead, 55 | APIOnly: false, 56 | Next: nil, 57 | index: newIndex(viewBag{ 58 | defaultTitle, 59 | defaultRefresh, 60 | defaultFontURL, 61 | defaultChartJSURL, 62 | defaultCustomHead, 63 | }), 64 | } 65 | 66 | func configDefault(config ...Config) Config { 67 | // Users can change ConfigDefault.Title/Refresh which then 68 | // become incompatible with ConfigDefault.index 69 | if ConfigDefault.Title != defaultTitle || 70 | ConfigDefault.Refresh != defaultRefresh || 71 | ConfigDefault.FontURL != defaultFontURL || 72 | ConfigDefault.ChartJSURL != defaultChartJSURL || 73 | ConfigDefault.CustomHead != defaultCustomHead { 74 | if ConfigDefault.Refresh < minRefresh { 75 | ConfigDefault.Refresh = minRefresh 76 | } 77 | // update default index with new default title/refresh 78 | ConfigDefault.index = newIndex(viewBag{ 79 | ConfigDefault.Title, 80 | ConfigDefault.Refresh, 81 | ConfigDefault.FontURL, 82 | ConfigDefault.ChartJSURL, 83 | ConfigDefault.CustomHead, 84 | }) 85 | } 86 | 87 | // Return default config if nothing provided 88 | if len(config) < 1 { 89 | return ConfigDefault 90 | } 91 | 92 | // Override default config 93 | cfg := config[0] 94 | 95 | // Set default values 96 | if cfg.Title == "" { 97 | cfg.Title = ConfigDefault.Title 98 | } 99 | 100 | if cfg.Refresh == 0 { 101 | cfg.Refresh = ConfigDefault.Refresh 102 | } 103 | if cfg.FontURL == "" { 104 | cfg.FontURL = defaultFontURL 105 | } 106 | 107 | if cfg.ChartJSURL == "" { 108 | cfg.ChartJSURL = defaultChartJSURL 109 | } 110 | if cfg.Refresh < minRefresh { 111 | cfg.Refresh = minRefresh 112 | } 113 | 114 | if cfg.Next == nil { 115 | cfg.Next = ConfigDefault.Next 116 | } 117 | 118 | if !cfg.APIOnly { 119 | cfg.APIOnly = ConfigDefault.APIOnly 120 | } 121 | 122 | // update cfg.index with custom title/refresh 123 | cfg.index = newIndex(viewBag{ 124 | title: cfg.Title, 125 | refresh: cfg.Refresh, 126 | fontURL: cfg.FontURL, 127 | chartJSURL: cfg.ChartJSURL, 128 | customHead: cfg.CustomHead, 129 | }) 130 | 131 | return cfg 132 | } 133 | -------------------------------------------------------------------------------- /monitor/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/monitor 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/shirou/gopsutil/v4 v4.25.7 8 | github.com/stretchr/testify v1.11.1 9 | github.com/valyala/fasthttp v1.65.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/ebitengine/purego v0.8.4 // indirect 16 | github.com/go-ole/go-ole v1.2.6 // indirect 17 | github.com/gofiber/schema v1.6.0 // indirect 18 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 19 | github.com/google/uuid v1.6.0 // indirect 20 | github.com/klauspost/compress v1.18.0 // indirect 21 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 22 | github.com/mattn/go-colorable v0.1.14 // indirect 23 | github.com/mattn/go-isatty v0.0.20 // indirect 24 | github.com/philhofer/fwd v1.2.0 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 27 | github.com/tinylib/msgp v1.4.0 // indirect 28 | github.com/tklauser/go-sysconf v0.3.15 // indirect 29 | github.com/tklauser/numcpus v0.10.0 // indirect 30 | github.com/valyala/bytebufferpool v1.0.0 // indirect 31 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 32 | golang.org/x/crypto v0.42.0 // indirect 33 | golang.org/x/net v0.44.0 // indirect 34 | golang.org/x/sys v0.36.0 // indirect 35 | golang.org/x/text v0.29.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /monitor/monitor.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/gofiber/fiber/v3" 11 | "github.com/shirou/gopsutil/v4/cpu" 12 | "github.com/shirou/gopsutil/v4/load" 13 | "github.com/shirou/gopsutil/v4/mem" 14 | "github.com/shirou/gopsutil/v4/net" 15 | "github.com/shirou/gopsutil/v4/process" 16 | ) 17 | 18 | type stats struct { 19 | PID statsPID `json:"pid"` 20 | OS statsOS `json:"os"` 21 | } 22 | 23 | type statsPID struct { 24 | CPU float64 `json:"cpu"` 25 | RAM uint64 `json:"ram"` 26 | Conns int `json:"conns"` 27 | } 28 | 29 | type statsOS struct { 30 | CPU float64 `json:"cpu"` 31 | RAM uint64 `json:"ram"` 32 | TotalRAM uint64 `json:"total_ram"` 33 | LoadAvg float64 `json:"load_avg"` 34 | Conns int `json:"conns"` 35 | } 36 | 37 | var ( 38 | monitPIDCPU atomic.Value 39 | monitPIDRAM atomic.Value 40 | monitPIDConns atomic.Value 41 | 42 | monitOSCPU atomic.Value 43 | monitOSRAM atomic.Value 44 | monitOSTotalRAM atomic.Value 45 | monitOSLoadAvg atomic.Value 46 | monitOSConns atomic.Value 47 | ) 48 | 49 | var ( 50 | mutex sync.RWMutex 51 | once sync.Once 52 | data = &stats{} 53 | ) 54 | 55 | // New creates a new middleware handler 56 | func New(config ...Config) fiber.Handler { 57 | // Set default config 58 | cfg := configDefault(config...) 59 | 60 | // Start routine to update statistics 61 | once.Do(func() { 62 | p, _ := process.NewProcess(int32(os.Getpid())) //nolint:errcheck // TODO: Handle error 63 | numcpu := runtime.NumCPU() 64 | updateStatistics(p, numcpu) 65 | 66 | go func() { 67 | for { 68 | time.Sleep(cfg.Refresh) 69 | 70 | updateStatistics(p, numcpu) 71 | } 72 | }() 73 | }) 74 | 75 | // Return new handler 76 | //nolint:errcheck // Ignore the type-assertion errors 77 | return func(c fiber.Ctx) error { 78 | // Don't execute middleware if Next returns true 79 | if cfg.Next != nil && cfg.Next(&c) { 80 | return c.Next() 81 | } 82 | 83 | if c.Method() != fiber.MethodGet { 84 | return fiber.ErrMethodNotAllowed 85 | } 86 | if c.Get(fiber.HeaderAccept) == fiber.MIMEApplicationJSON || cfg.APIOnly { 87 | mutex.Lock() 88 | data.PID.CPU, _ = monitPIDCPU.Load().(float64) 89 | data.PID.RAM, _ = monitPIDRAM.Load().(uint64) 90 | data.PID.Conns, _ = monitPIDConns.Load().(int) 91 | 92 | data.OS.CPU, _ = monitOSCPU.Load().(float64) 93 | data.OS.RAM, _ = monitOSRAM.Load().(uint64) 94 | data.OS.TotalRAM, _ = monitOSTotalRAM.Load().(uint64) 95 | data.OS.LoadAvg, _ = monitOSLoadAvg.Load().(float64) 96 | data.OS.Conns, _ = monitOSConns.Load().(int) 97 | mutex.Unlock() 98 | return c.Status(fiber.StatusOK).JSON(data) 99 | } 100 | c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) 101 | return c.Status(fiber.StatusOK).SendString(cfg.index) 102 | } 103 | } 104 | 105 | func updateStatistics(p *process.Process, numcpu int) { 106 | pidCPU, err := p.Percent(0) 107 | if err == nil { 108 | monitPIDCPU.Store(pidCPU / float64(numcpu)) 109 | } 110 | 111 | if osCPU, err := cpu.Percent(0, false); err == nil && len(osCPU) > 0 { 112 | monitOSCPU.Store(osCPU[0]) 113 | } 114 | 115 | if pidRAM, err := p.MemoryInfo(); err == nil && pidRAM != nil { 116 | monitPIDRAM.Store(pidRAM.RSS) 117 | } 118 | 119 | if osRAM, err := mem.VirtualMemory(); err == nil && osRAM != nil { 120 | monitOSRAM.Store(osRAM.Used) 121 | monitOSTotalRAM.Store(osRAM.Total) 122 | } 123 | 124 | if loadAvg, err := load.Avg(); err == nil && loadAvg != nil { 125 | monitOSLoadAvg.Store(loadAvg.Load1) 126 | } 127 | 128 | pidConns, err := net.ConnectionsPid("tcp", p.Pid) 129 | if err == nil { 130 | monitPIDConns.Store(len(pidConns)) 131 | } 132 | 133 | osConns, err := net.Connections("tcp") 134 | if err == nil { 135 | monitOSConns.Store(len(osConns)) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /opafiber/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: opafiber 3 | --- 4 | 5 | # Opafiber 6 | 7 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=opafiber*) 8 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 9 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20opafiber/badge.svg) 10 | 11 | [Open Policy Agent](https://github.com/open-policy-agent/opa) support for Fiber. 12 | 13 | **Note: Requires Go 1.25 and above** 14 | 15 | ## Install 16 | 17 | ``` 18 | go get -u github.com/gofiber/fiber/v3 19 | go get -u github.com/gofiber/contrib/opafiber/v2 20 | ``` 21 | 22 | ## Signature 23 | 24 | ```go 25 | opafiber.New(config opafiber.Config) fiber.Handler 26 | 27 | ``` 28 | 29 | ## Config 30 | 31 | | Property | Type | Description | Default | 32 | |:----------------------|:--------------------|:-------------------------------------------------------------|:--------------------------------------------------------------------| 33 | | RegoQuery | `string` | Required - Rego query | - | 34 | | RegoPolicy | `io.Reader` | Required - Rego policy | - | 35 | | IncludeQueryString | `bool` | Include query string as input to rego policy | `false` | 36 | | DeniedStatusCode | `int` | Http status code to return when policy denies request | `400` | 37 | | DeniedResponseMessage | `string` | Http response body text to return when policy denies request | `""` | 38 | | IncludeHeaders | `[]string` | Include headers as input to rego policy | - | 39 | | InputCreationMethod | `InputCreationFunc` | Use your own function to provide input for OPA | `func defaultInput(ctx *fiber.Ctx) (map[string]interface{}, error)` | 40 | 41 | ## Types 42 | 43 | ```go 44 | type InputCreationFunc func(c *fiber.Ctx) (map[string]interface{}, error) 45 | ``` 46 | 47 | ## Usage 48 | 49 | OPA Fiber middleware sends the following example data to the policy engine as input: 50 | 51 | ```json 52 | { 53 | "method": "GET", 54 | "path": "/somePath", 55 | "query": { 56 | "name": ["John Doe"] 57 | }, 58 | "headers": { 59 | "Accept": "application/json", 60 | "Content-Type": "application/json" 61 | } 62 | } 63 | ``` 64 | 65 | ```go 66 | package main 67 | 68 | import ( 69 | "github.com/gofiber/fiber/v3" 70 | "github.com/gofiber/contrib/opafiber/v2" 71 | ) 72 | 73 | func main() { 74 | app := fiber.New() 75 | module := ` 76 | package example.authz 77 | 78 | default allow := false 79 | 80 | allow { 81 | input.method == "GET" 82 | } 83 | ` 84 | 85 | cfg := opafiber.Config{ 86 | RegoQuery: "data.example.authz.allow", 87 | RegoPolicy: bytes.NewBufferString(module), 88 | IncludeQueryString: true, 89 | DeniedStatusCode: fiber.StatusForbidden, 90 | DeniedResponseMessage: "status forbidden", 91 | IncludeHeaders: []string{"Authorization"}, 92 | InputCreationMethod: func (ctx *fiber.Ctx) (map[string]interface{}, error) { 93 | return map[string]interface{}{ 94 | "method": ctx.Method(), 95 | "path": ctx.Path(), 96 | }, nil 97 | }, 98 | } 99 | app.Use(opafiber.New(cfg)) 100 | 101 | app.Get("/", func(ctx *fiber.Ctx) error { 102 | return ctx.SendStatus(200) 103 | }) 104 | 105 | app.Listen(":8080") 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /opafiber/fiber.go: -------------------------------------------------------------------------------- 1 | package opafiber 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/gofiber/fiber/v3" 9 | "github.com/gofiber/utils/v2" 10 | "github.com/open-policy-agent/opa/rego" 11 | ) 12 | 13 | type InputCreationFunc func(c fiber.Ctx) (map[string]interface{}, error) 14 | 15 | type Config struct { 16 | RegoPolicy io.Reader 17 | RegoQuery string 18 | IncludeHeaders []string 19 | IncludeQueryString bool 20 | DeniedStatusCode int 21 | DeniedResponseMessage string 22 | InputCreationMethod InputCreationFunc 23 | } 24 | 25 | func New(cfg Config) fiber.Handler { 26 | err := cfg.fillAndValidate() 27 | if err != nil { 28 | panic(err) 29 | } 30 | readedBytes, err := io.ReadAll(cfg.RegoPolicy) 31 | if err != nil { 32 | panic(fmt.Sprint("could not read rego policy %w", err)) 33 | } 34 | query, err := rego.New( 35 | rego.Query(cfg.RegoQuery), 36 | rego.Module("policy.rego", utils.UnsafeString(readedBytes)), 37 | ).PrepareForEval(context.Background()) 38 | if err != nil { 39 | panic(fmt.Sprint("rego policy error: %w", err)) 40 | } 41 | return func(c fiber.Ctx) error { 42 | input, err := cfg.InputCreationMethod(c) 43 | if err != nil { 44 | c.Response().SetStatusCode(fiber.StatusInternalServerError) 45 | c.Response().SetBodyString(fmt.Sprintf("Error creating input: %s", err)) 46 | return err 47 | } 48 | if cfg.IncludeQueryString { 49 | queryStringData := make(map[string][]string) 50 | c.Request().URI().QueryArgs().VisitAll(func(key, value []byte) { 51 | queryStringData[utils.UnsafeString(key)] = append(queryStringData[utils.UnsafeString(key)], utils.UnsafeString(value)) 52 | }) 53 | input["query"] = queryStringData 54 | } 55 | if len(cfg.IncludeHeaders) > 0 { 56 | headers := make(map[string]string) 57 | for _, header := range cfg.IncludeHeaders { 58 | headers[header] = c.Get(header) 59 | } 60 | input["headers"] = headers 61 | } 62 | res, err := query.Eval(context.Background(), rego.EvalInput(input)) 63 | if err != nil { 64 | c.Response().SetStatusCode(fiber.StatusInternalServerError) 65 | c.Response().SetBodyString(fmt.Sprintf("Error evaluating rego policy: %s", err)) 66 | return err 67 | } 68 | 69 | if !res.Allowed() { 70 | c.Response().SetStatusCode(cfg.DeniedStatusCode) 71 | c.Response().SetBodyString(cfg.DeniedResponseMessage) 72 | return nil 73 | } 74 | 75 | return c.Next() 76 | } 77 | } 78 | 79 | func (c *Config) fillAndValidate() error { 80 | if c.RegoQuery == "" { 81 | return fmt.Errorf("rego query can not be empty") 82 | } 83 | 84 | if c.DeniedStatusCode == 0 { 85 | c.DeniedStatusCode = fiber.StatusBadRequest 86 | } 87 | if c.DeniedResponseMessage == "" { 88 | c.DeniedResponseMessage = fiber.ErrBadRequest.Error() 89 | } 90 | if c.IncludeHeaders == nil { 91 | c.IncludeHeaders = []string{} 92 | } 93 | if c.InputCreationMethod == nil { 94 | c.InputCreationMethod = defaultInput 95 | } 96 | return nil 97 | } 98 | 99 | func defaultInput(ctx fiber.Ctx) (map[string]interface{}, error) { 100 | input := map[string]interface{}{ 101 | "method": ctx.Method(), 102 | "path": ctx.Path(), 103 | } 104 | return input, nil 105 | } 106 | -------------------------------------------------------------------------------- /opafiber/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/opafiber/v2 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/gofiber/utils/v2 v2.0.0-rc.1 8 | github.com/open-policy-agent/opa v0.61.0 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/OneOfOne/xxhash v1.2.8 // indirect 14 | github.com/agnivade/levenshtein v1.1.1 // indirect 15 | github.com/andybalholm/brotli v1.2.0 // indirect 16 | github.com/beorn7/perks v1.0.1 // indirect 17 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/go-ini/ini v1.67.0 // indirect 20 | github.com/go-logr/logr v1.4.1 // indirect 21 | github.com/go-logr/stdr v1.2.2 // indirect 22 | github.com/gobwas/glob v0.2.3 // indirect 23 | github.com/gofiber/schema v1.6.0 // indirect 24 | github.com/google/uuid v1.6.0 // indirect 25 | github.com/gorilla/mux v1.8.1 // indirect 26 | github.com/klauspost/compress v1.18.0 // indirect 27 | github.com/mattn/go-colorable v0.1.14 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 30 | github.com/philhofer/fwd v1.2.0 // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/prometheus/client_golang v1.18.0 // indirect 33 | github.com/prometheus/client_model v0.5.0 // indirect 34 | github.com/prometheus/common v0.45.0 // indirect 35 | github.com/prometheus/procfs v0.12.0 // indirect 36 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect 37 | github.com/sirupsen/logrus v1.9.3 // indirect 38 | github.com/tchap/go-patricia/v2 v2.3.1 // indirect 39 | github.com/tinylib/msgp v1.4.0 // indirect 40 | github.com/valyala/bytebufferpool v1.0.0 // indirect 41 | github.com/valyala/fasthttp v1.65.0 // indirect 42 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 43 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 44 | github.com/yashtewari/glob-intersection v0.2.0 // indirect 45 | go.opentelemetry.io/otel v1.21.0 // indirect 46 | go.opentelemetry.io/otel/metric v1.21.0 // indirect 47 | go.opentelemetry.io/otel/sdk v1.21.0 // indirect 48 | go.opentelemetry.io/otel/trace v1.21.0 // indirect 49 | golang.org/x/crypto v0.42.0 // indirect 50 | golang.org/x/net v0.44.0 // indirect 51 | golang.org/x/sys v0.36.0 // indirect 52 | golang.org/x/text v0.29.0 // indirect 53 | google.golang.org/protobuf v1.33.0 // indirect 54 | gopkg.in/yaml.v2 v2.4.0 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | sigs.k8s.io/yaml v1.4.0 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /otelfiber/config.go: -------------------------------------------------------------------------------- 1 | package otelfiber 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v3" 5 | "go.opentelemetry.io/otel/attribute" 6 | otelmetric "go.opentelemetry.io/otel/metric" 7 | "go.opentelemetry.io/otel/propagation" 8 | oteltrace "go.opentelemetry.io/otel/trace" 9 | ) 10 | 11 | // config is used to configure the Fiber middleware. 12 | type config struct { 13 | Next func(fiber.Ctx) bool 14 | TracerProvider oteltrace.TracerProvider 15 | MeterProvider otelmetric.MeterProvider 16 | Port *int 17 | Propagators propagation.TextMapPropagator 18 | SpanNameFormatter func(fiber.Ctx) string 19 | CustomAttributes func(fiber.Ctx) []attribute.KeyValue 20 | CustomMetricAttributes func(fiber.Ctx) []attribute.KeyValue 21 | collectClientIP bool 22 | withoutMetrics bool 23 | } 24 | 25 | // Option specifies instrumentation configuration options. 26 | type Option interface { 27 | apply(*config) 28 | } 29 | 30 | type optionFunc func(*config) 31 | 32 | func (o optionFunc) apply(c *config) { 33 | o(c) 34 | } 35 | 36 | // WithNext takes a function that will be called on every 37 | // request, the middleware will be skipped if returning true 38 | func WithNext(f func(ctx fiber.Ctx) bool) Option { 39 | return optionFunc(func(cfg *config) { 40 | cfg.Next = f 41 | }) 42 | } 43 | 44 | // WithPropagators specifies propagators to use for extracting 45 | // information from the HTTP requests. If none are specified, global 46 | // ones will be used. 47 | func WithPropagators(propagators propagation.TextMapPropagator) Option { 48 | return optionFunc(func(cfg *config) { 49 | cfg.Propagators = propagators 50 | }) 51 | } 52 | 53 | // WithTracerProvider specifies a tracer provider to use for creating a tracer. 54 | // If none is specified, the global provider is used. 55 | func WithTracerProvider(provider oteltrace.TracerProvider) Option { 56 | return optionFunc(func(cfg *config) { 57 | cfg.TracerProvider = provider 58 | }) 59 | } 60 | 61 | // WithMeterProvider specifies a meter provider to use for reporting. 62 | // If none is specified, the global provider is used. 63 | func WithMeterProvider(provider otelmetric.MeterProvider) Option { 64 | return optionFunc(func(cfg *config) { 65 | cfg.MeterProvider = provider 66 | }) 67 | } 68 | 69 | // WithSpanNameFormatter takes a function that will be called on every 70 | // request and the returned string will become the Span Name 71 | func WithSpanNameFormatter(f func(ctx fiber.Ctx) string) Option { 72 | return optionFunc(func(cfg *config) { 73 | cfg.SpanNameFormatter = f 74 | }) 75 | } 76 | 77 | // WithPort specifies the value to use when setting the `server.port` 78 | // attribute on metrics/spans. Attribute is "Conditionally Required: If not 79 | // default (`80` for `http`, `443` for `https`). 80 | func WithPort(port int) Option { 81 | return optionFunc(func(cfg *config) { 82 | cfg.Port = &port 83 | }) 84 | } 85 | 86 | // WithCustomAttributes specifies a function that will be called on every 87 | // request and the returned attributes will be added to the span. 88 | func WithCustomAttributes(f func(ctx fiber.Ctx) []attribute.KeyValue) Option { 89 | return optionFunc(func(cfg *config) { 90 | cfg.CustomAttributes = f 91 | }) 92 | } 93 | 94 | // WithCustomMetricAttributes specifies a function that will be called on every 95 | // request and the returned attributes will be added to the metrics. 96 | func WithCustomMetricAttributes(f func(ctx fiber.Ctx) []attribute.KeyValue) Option { 97 | return optionFunc(func(cfg *config) { 98 | cfg.CustomMetricAttributes = f 99 | }) 100 | } 101 | 102 | // WithCollectClientIP specifies whether to collect the client's IP address 103 | // from the request. This is enabled by default. 104 | func WithCollectClientIP(collect bool) Option { 105 | return optionFunc(func(cfg *config) { 106 | cfg.collectClientIP = collect 107 | }) 108 | } 109 | 110 | // WithoutMetrics disables metrics collection when set to true 111 | func WithoutMetrics(withoutMetrics bool) Option { 112 | return optionFunc(func(cfg *config) { 113 | cfg.withoutMetrics = withoutMetrics 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /otelfiber/doc.go: -------------------------------------------------------------------------------- 1 | // Package otelfiber instruments the github.com/gofiber/fiber package. 2 | // (https://github.com/gofiber/fiber). 3 | // 4 | // Currently, only the routing of a received message can be instrumented. To do 5 | // so, use the Middleware function. 6 | package otelfiber // import" github.com/gofiber/contrib/otelfiber" 7 | -------------------------------------------------------------------------------- /otelfiber/example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS base 2 | COPY . /src/ 3 | WORKDIR /src/instrumentation/github.com/gofiber/fiber/otelefiber/example 4 | 5 | FROM base AS fiber-server 6 | RUN go install ./server.go 7 | CMD ["/go/bin/server"] 8 | -------------------------------------------------------------------------------- /otelfiber/example/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: otelfiber-example 3 | --- 4 | 5 | # Example 6 | 7 | An HTTP server using gofiber fiber and instrumentation. The server has a 8 | `/users/:id` endpoint. The server generates span information to 9 | `stdout`. 10 | 11 | These instructions expect you have 12 | [docker-compose](https://docs.docker.com/compose/) installed. 13 | 14 | Bring up the `fiber-server` and `fiber-client` services to run the 15 | example: 16 | 17 | ```sh 18 | docker-compose up --detach fiber-server fiber-client 19 | ``` 20 | 21 | The `fiber-client` service sends just one HTTP request to `fiber-server` 22 | and then exits. View the span generated by `fiber-server` in the logs: 23 | 24 | ```sh 25 | docker-compose logs fiber-server 26 | ``` 27 | 28 | Shut down the services when you are finished with the example: 29 | 30 | ```sh 31 | docker-compose down 32 | ``` 33 | -------------------------------------------------------------------------------- /otelfiber/example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | fiber-client: 4 | image: golang:alpine 5 | networks: 6 | - example 7 | command: 8 | - "/bin/sh" 9 | - "-c" 10 | - "wget http://fiber-server:3000/users/123 && cat 123" 11 | depends_on: 12 | - fiber-server 13 | fiber-server: 14 | build: 15 | dockerfile: $PWD/Dockerfile 16 | ports: 17 | - "3000:80" 18 | command: 19 | - "/bin/sh" 20 | - "-c" 21 | - "/go/bin/server" 22 | networks: 23 | - example 24 | networks: 25 | example: 26 | -------------------------------------------------------------------------------- /otelfiber/example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/otelfiber/example 2 | 3 | go 1.25.0 4 | 5 | replace github.com/gofiber/contrib/otelfiber => ../ 6 | 7 | require ( 8 | github.com/gofiber/contrib/otelfiber v1.0.9 9 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 10 | go.opentelemetry.io/otel v1.37.0 11 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 12 | go.opentelemetry.io/otel/sdk v1.37.0 13 | go.opentelemetry.io/otel/trace v1.37.0 14 | ) 15 | 16 | require ( 17 | github.com/andybalholm/brotli v1.2.0 // indirect 18 | github.com/go-logr/logr v1.4.3 // indirect 19 | github.com/go-logr/stdr v1.2.2 // indirect 20 | github.com/gofiber/contrib/otelfiber/v2 v2.2.3 // indirect 21 | github.com/gofiber/schema v1.6.0 // indirect 22 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/klauspost/compress v1.18.0 // indirect 25 | github.com/mattn/go-colorable v0.1.14 // indirect 26 | github.com/mattn/go-isatty v0.0.20 // indirect 27 | github.com/philhofer/fwd v1.2.0 // indirect 28 | github.com/tinylib/msgp v1.4.0 // indirect 29 | github.com/valyala/bytebufferpool v1.0.0 // indirect 30 | github.com/valyala/fasthttp v1.65.0 // indirect 31 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 32 | go.opentelemetry.io/contrib v1.20.0 // indirect 33 | go.opentelemetry.io/otel/metric v1.37.0 // indirect 34 | golang.org/x/crypto v0.42.0 // indirect 35 | golang.org/x/net v0.44.0 // indirect 36 | golang.org/x/sys v0.36.0 // indirect 37 | golang.org/x/text v0.29.0 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /otelfiber/example/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | 8 | "go.opentelemetry.io/otel/sdk/resource" 9 | 10 | "github.com/gofiber/fiber/v3" 11 | 12 | "github.com/gofiber/contrib/otelfiber" 13 | "go.opentelemetry.io/otel" 14 | "go.opentelemetry.io/otel/attribute" 15 | stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 16 | 17 | //"go.opentelemetry.io/otel/exporters/jaeger" 18 | "go.opentelemetry.io/otel/propagation" 19 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 20 | semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 21 | oteltrace "go.opentelemetry.io/otel/trace" 22 | ) 23 | 24 | var tracer = otel.Tracer("fiber-server") 25 | 26 | func main() { 27 | tp := initTracer() 28 | defer func() { 29 | if err := tp.Shutdown(context.Background()); err != nil { 30 | log.Printf("Error shutting down tracer provider: %v", err) 31 | } 32 | }() 33 | 34 | app := fiber.New() 35 | 36 | // customise span name 37 | //app.Use(otelfiber.Middleware(otelfiber.WithSpanNameFormatter(func(ctx fiber.Ctx) string { 38 | // return fmt.Sprintf("%s - %s", ctx.Method(), ctx.Route().Path) 39 | //}))) 40 | 41 | app.Use(otelfiber.Middleware()) 42 | 43 | app.Get("/error", func(ctx fiber.Ctx) error { 44 | return errors.New("abc") 45 | }) 46 | 47 | app.Get("/users/:id", func(c fiber.Ctx) error { 48 | id := c.Params("id") 49 | name := getUser(c, id) 50 | return c.JSON(fiber.Map{"id": id, name: name}) 51 | }) 52 | 53 | log.Fatal(app.Listen(":3000")) 54 | } 55 | 56 | func initTracer() *sdktrace.TracerProvider { 57 | exporter, err := stdout.New(stdout.WithPrettyPrint()) 58 | //exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces"))) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | tp := sdktrace.NewTracerProvider( 63 | sdktrace.WithSampler(sdktrace.AlwaysSample()), 64 | sdktrace.WithBatcher(exporter), 65 | sdktrace.WithResource( 66 | resource.NewWithAttributes( 67 | semconv.SchemaURL, 68 | semconv.ServiceNameKey.String("my-service"), 69 | )), 70 | ) 71 | otel.SetTracerProvider(tp) 72 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 73 | return tp 74 | } 75 | 76 | func getUser(ctx context.Context, id string) string { 77 | _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) 78 | defer span.End() 79 | if id == "123" { 80 | return "otelfiber tester" 81 | } 82 | return "unknown" 83 | } 84 | -------------------------------------------------------------------------------- /otelfiber/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/otelfiber/v2 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/gofiber/utils/v2 v2.0.0-rc.1 8 | github.com/stretchr/testify v1.11.1 9 | go.opentelemetry.io/contrib v1.20.0 10 | go.opentelemetry.io/contrib/propagators/b3 v1.20.0 11 | go.opentelemetry.io/otel v1.19.0 12 | go.opentelemetry.io/otel/metric v1.19.0 13 | go.opentelemetry.io/otel/sdk v1.19.0 14 | go.opentelemetry.io/otel/sdk/metric v0.41.0 15 | go.opentelemetry.io/otel/trace v1.19.0 16 | ) 17 | 18 | require ( 19 | github.com/andybalholm/brotli v1.2.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/go-logr/logr v1.2.4 // indirect 22 | github.com/go-logr/stdr v1.2.2 // indirect 23 | github.com/gofiber/schema v1.6.0 // indirect 24 | github.com/google/uuid v1.6.0 // indirect 25 | github.com/klauspost/compress v1.18.0 // indirect 26 | github.com/mattn/go-colorable v0.1.14 // indirect 27 | github.com/mattn/go-isatty v0.0.20 // indirect 28 | github.com/philhofer/fwd v1.2.0 // indirect 29 | github.com/pmezard/go-difflib v1.0.0 // indirect 30 | github.com/tinylib/msgp v1.4.0 // indirect 31 | github.com/valyala/bytebufferpool v1.0.0 // indirect 32 | github.com/valyala/fasthttp v1.65.0 // indirect 33 | golang.org/x/crypto v0.42.0 // indirect 34 | golang.org/x/net v0.44.0 // indirect 35 | golang.org/x/sys v0.36.0 // indirect 36 | golang.org/x/text v0.29.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /otelfiber/internal/http.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "go.opentelemetry.io/otel/codes" 8 | "go.opentelemetry.io/otel/trace" 9 | ) 10 | 11 | // SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message 12 | // as specified by the OpenTelemetry specification for a span. 13 | // Exclude 4xx for SERVER to set the appropriate status. 14 | func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) { 15 | // This code block ignores the HTTP 306 status code. The 306 status code is no longer in use. 16 | if http.StatusText(code) == "" { 17 | return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) 18 | } 19 | 20 | if (code >= http.StatusContinue && code < http.StatusBadRequest) || 21 | (spanKind == trace.SpanKindServer && isCode4xx(code)) { 22 | return codes.Unset, "" 23 | } 24 | return codes.Error, "" 25 | } 26 | 27 | func isCode4xx(code int) bool { 28 | return code >= http.StatusBadRequest && code <= http.StatusUnavailableForLegalReasons 29 | } 30 | -------------------------------------------------------------------------------- /otelfiber/internal/http_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "go.opentelemetry.io/otel/codes" 6 | oteltrace "go.opentelemetry.io/otel/trace" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | func TestIsCode4xxIsNotValid(t *testing.T) { 12 | response := isCode4xx(http.StatusOK) 13 | 14 | assert.False(t, response) 15 | } 16 | 17 | func TestIsCode4xxIsValid(t *testing.T) { 18 | response := isCode4xx(http.StatusNotFound) 19 | 20 | assert.True(t, response) 21 | } 22 | 23 | func TestStatusErrorWithMessage(t *testing.T) { 24 | spanStatus, spanMessage := SpanStatusFromHTTPStatusCodeAndSpanKind(600, oteltrace.SpanKindClient) 25 | 26 | assert.Equal(t, codes.Error, spanStatus) 27 | assert.Equal(t, "Invalid HTTP status code 600", spanMessage) 28 | } 29 | 30 | func TestStatusErrorWithMessageForIgnoredHTTPCode(t *testing.T) { 31 | spanStatus, spanMessage := SpanStatusFromHTTPStatusCodeAndSpanKind(306, oteltrace.SpanKindClient) 32 | 33 | assert.Equal(t, codes.Error, spanStatus) 34 | assert.Equal(t, "Invalid HTTP status code 306", spanMessage) 35 | } 36 | 37 | func TestStatusErrorWhenHTTPCode5xx(t *testing.T) { 38 | spanStatus, spanMessage := SpanStatusFromHTTPStatusCodeAndSpanKind(http.StatusInternalServerError, oteltrace.SpanKindServer) 39 | 40 | assert.Equal(t, codes.Error, spanStatus) 41 | assert.Equal(t, "", spanMessage) 42 | } 43 | 44 | func TestStatusUnsetWhenServerSpanAndBadRequest(t *testing.T) { 45 | spanStatus, spanMessage := SpanStatusFromHTTPStatusCodeAndSpanKind(http.StatusBadRequest, oteltrace.SpanKindServer) 46 | 47 | assert.Equal(t, codes.Unset, spanStatus) 48 | assert.Equal(t, "", spanMessage) 49 | } 50 | 51 | func TestStatusUnset(t *testing.T) { 52 | spanStatus, spanMessage := SpanStatusFromHTTPStatusCodeAndSpanKind(http.StatusOK, oteltrace.SpanKindClient) 53 | 54 | assert.Equal(t, codes.Unset, spanStatus) 55 | assert.Equal(t, "", spanMessage) 56 | } 57 | -------------------------------------------------------------------------------- /otelfiber/semconv.go: -------------------------------------------------------------------------------- 1 | package otelfiber 2 | 3 | import ( 4 | "encoding/base64" 5 | "strings" 6 | 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/gofiber/utils/v2" 9 | "go.opentelemetry.io/otel/attribute" 10 | semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 11 | ) 12 | 13 | var ( 14 | httpProtocolNameAttr = semconv.NetworkProtocolName("http") 15 | http11VersionAttr = semconv.NetworkProtocolVersion("1.1") 16 | http10VersionAttr = semconv.NetworkProtocolVersion("1.0") 17 | ) 18 | 19 | func httpServerMetricAttributesFromRequest(c fiber.Ctx, cfg config) []attribute.KeyValue { 20 | protocolAttributes := httpNetworkProtocolAttributes(c) 21 | attrs := []attribute.KeyValue{ 22 | semconv.URLScheme(utils.CopyString(c.Protocol())), 23 | semconv.ServerAddress(utils.CopyString(c.Hostname())), 24 | semconv.HTTPRequestMethodKey.String(utils.CopyString(c.Method())), 25 | } 26 | attrs = append(attrs, protocolAttributes...) 27 | 28 | if cfg.Port != nil { 29 | attrs = append(attrs, semconv.ServerPort(*cfg.Port)) 30 | } 31 | 32 | if cfg.CustomMetricAttributes != nil { 33 | attrs = append(attrs, cfg.CustomMetricAttributes(c)...) 34 | } 35 | 36 | return attrs 37 | } 38 | 39 | func httpServerTraceAttributesFromRequest(c fiber.Ctx, cfg config) []attribute.KeyValue { 40 | protocolAttributes := httpNetworkProtocolAttributes(c) 41 | attrs := []attribute.KeyValue{ 42 | // utils.CopyString: we need to copy the string as fasthttp strings are by default 43 | // mutable so it will be unsafe to use in this middleware as it might be used after 44 | // the handler returns. 45 | semconv.HTTPRequestMethodKey.String(utils.CopyString(c.Method())), 46 | semconv.URLScheme(utils.CopyString(c.Protocol())), 47 | semconv.HTTPRequestBodySize(c.Request().Header.ContentLength()), 48 | semconv.URLPath(string(utils.CopyBytes(c.Request().URI().Path()))), 49 | semconv.URLQuery(c.Request().URI().QueryArgs().String()), 50 | semconv.URLFull(utils.CopyString(c.OriginalURL())), 51 | semconv.UserAgentOriginal(string(utils.CopyBytes(c.Request().Header.UserAgent()))), 52 | semconv.ServerAddress(utils.CopyString(c.Hostname())), 53 | semconv.NetTransportTCP, 54 | } 55 | attrs = append(attrs, protocolAttributes...) 56 | 57 | if cfg.Port != nil { 58 | attrs = append(attrs, semconv.NetHostPortKey.Int(*cfg.Port)) 59 | } 60 | 61 | if username, ok := HasBasicAuth(c.Get(fiber.HeaderAuthorization)); ok { 62 | attrs = append(attrs, semconv.EnduserIDKey.String(utils.CopyString(username))) 63 | } 64 | 65 | if cfg.collectClientIP { 66 | clientIP := c.IP() 67 | if len(clientIP) > 0 { 68 | attrs = append(attrs, semconv.ClientAddress(utils.CopyString(clientIP))) 69 | } 70 | } 71 | 72 | if cfg.CustomAttributes != nil { 73 | attrs = append(attrs, cfg.CustomAttributes(c)...) 74 | } 75 | 76 | return attrs 77 | } 78 | 79 | func httpNetworkProtocolAttributes(c fiber.Ctx) []attribute.KeyValue { 80 | httpProtocolAttributes := []attribute.KeyValue{httpProtocolNameAttr} 81 | if c.Request().Header.IsHTTP11() { 82 | return append(httpProtocolAttributes, http11VersionAttr) 83 | } 84 | return append(httpProtocolAttributes, http10VersionAttr) 85 | } 86 | 87 | func HasBasicAuth(auth string) (string, bool) { 88 | if auth == "" { 89 | return "", false 90 | } 91 | 92 | // Check if the Authorization header is Basic 93 | if !strings.HasPrefix(auth, "Basic ") { 94 | return "", false 95 | } 96 | 97 | // Decode the header contents 98 | raw, err := base64.StdEncoding.DecodeString(auth[6:]) 99 | if err != nil { 100 | return "", false 101 | } 102 | 103 | // Get the credentials 104 | creds := utils.UnsafeString(raw) 105 | 106 | // Check if the credentials are in the correct form 107 | // which is "username:password". 108 | index := strings.Index(creds, ":") 109 | if index == -1 { 110 | return "", false 111 | } 112 | 113 | // Get the username 114 | return creds[:index], true 115 | } 116 | -------------------------------------------------------------------------------- /paseto/config.go: -------------------------------------------------------------------------------- 1 | package pasetoware 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ed25519" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/gofiber/fiber/v3" 12 | "github.com/gofiber/fiber/v3/extractors" 13 | "github.com/o1egl/paseto" 14 | "golang.org/x/crypto/chacha20poly1305" 15 | ) 16 | 17 | // Config defines the config for PASETO middleware 18 | type Config struct { 19 | // Next defines a function to skip this middleware when returned true. 20 | // 21 | // Optional. Default: nil 22 | Next func(fiber.Ctx) bool 23 | 24 | // SuccessHandler defines a function which is executed for a valid token. 25 | // Optional. Default: c.Next() 26 | SuccessHandler fiber.Handler 27 | 28 | // ErrorHandler defines a function which is executed for an invalid token. 29 | // It may be used to define a custom PASETO error. 30 | // Optional. Default: 401 Invalid or expired PASETO 31 | ErrorHandler fiber.ErrorHandler 32 | 33 | // Validate defines a function to validate if payload is valid 34 | // Optional. In case payload used is created using CreateToken function 35 | // If token is created using another function, this function must be provided 36 | Validate PayloadValidator 37 | 38 | // SymmetricKey to validate local tokens. 39 | // If it's set the middleware will use local tokens 40 | // 41 | // Required if PrivateKey and PublicKey are not set 42 | SymmetricKey []byte 43 | 44 | // PrivateKey to sign public tokens 45 | // 46 | // If it's set the middleware will use public tokens 47 | // Required if SymmetricKey is not set 48 | PrivateKey ed25519.PrivateKey 49 | 50 | // PublicKey to verify public tokens 51 | // 52 | // If it's set the middleware will use public tokens 53 | // Required if SymmetricKey is not set 54 | PublicKey crypto.PublicKey 55 | 56 | // Extractor defines a function to extract the token from the request. 57 | // Optional. Default: FromAuthHeader("Bearer"). 58 | Extractor extractors.Extractor 59 | } 60 | 61 | // ConfigDefault is the default config 62 | var ConfigDefault = Config{ 63 | SuccessHandler: nil, 64 | ErrorHandler: nil, 65 | Validate: nil, 66 | SymmetricKey: nil, 67 | Extractor: extractors.FromAuthHeader("Bearer"), 68 | } 69 | 70 | func defaultErrorHandler(c fiber.Ctx, err error) error { 71 | // default to badRequest if error is ErrMissingToken or any paseto decryption error 72 | errorStatus := fiber.StatusBadRequest 73 | if errors.Is(err, ErrDataUnmarshal) || errors.Is(err, ErrExpiredToken) { 74 | errorStatus = fiber.StatusUnauthorized 75 | } 76 | return c.Status(errorStatus).SendString(err.Error()) 77 | } 78 | 79 | func defaultValidateFunc(data []byte) (interface{}, error) { 80 | var payload paseto.JSONToken 81 | if err := json.Unmarshal(data, &payload); err != nil { 82 | return nil, ErrDataUnmarshal 83 | } 84 | 85 | if time.Now().After(payload.Expiration) { 86 | return nil, ErrExpiredToken 87 | } 88 | if err := payload.Validate( 89 | paseto.ValidAt(time.Now()), paseto.Subject(pasetoTokenSubject), 90 | paseto.ForAudience(pasetoTokenAudience), 91 | ); err != nil { 92 | return "", err 93 | } 94 | 95 | return payload.Get(pasetoTokenField), nil 96 | } 97 | 98 | // Helper function to set default values 99 | func configDefault(authConfigs ...Config) Config { 100 | // Return default authConfigs if nothing provided 101 | 102 | config := ConfigDefault 103 | if len(authConfigs) > 0 { 104 | // Override default authConfigs 105 | config = authConfigs[0] 106 | } 107 | 108 | // Set default values 109 | if config.SuccessHandler == nil { 110 | config.SuccessHandler = func(c fiber.Ctx) error { 111 | return c.Next() 112 | } 113 | } 114 | 115 | if config.ErrorHandler == nil { 116 | config.ErrorHandler = defaultErrorHandler 117 | } 118 | 119 | if config.Validate == nil { 120 | config.Validate = defaultValidateFunc 121 | } 122 | 123 | if config.Extractor.Extract == nil { 124 | config.Extractor = extractors.FromAuthHeader("Bearer") 125 | } 126 | 127 | if config.SymmetricKey != nil { 128 | if len(config.SymmetricKey) != chacha20poly1305.KeySize { 129 | panic( 130 | fmt.Sprintf( 131 | "Fiber: PASETO middleware requires a symmetric key with size %d", 132 | chacha20poly1305.KeySize, 133 | ), 134 | ) 135 | } 136 | 137 | if config.PublicKey != nil || config.PrivateKey != nil { 138 | panic("Fiber: PASETO middleware: can't use PublicKey or PrivateKey with SymmetricKey") 139 | } 140 | } else if config.PublicKey == nil || config.PrivateKey == nil { 141 | panic("Fiber: PASETO middleware: need both PublicKey and PrivateKey") 142 | } 143 | 144 | return config 145 | } 146 | -------------------------------------------------------------------------------- /paseto/config_test.go: -------------------------------------------------------------------------------- 1 | package pasetoware 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/gofiber/fiber/v3" 9 | "github.com/gofiber/fiber/v3/extractors" 10 | ) 11 | 12 | func assertRecoveryPanic(t *testing.T) { 13 | err := recover() 14 | assert.Equal(t, true, err != nil) 15 | } 16 | 17 | func Test_Config_No_SymmetricKey(t *testing.T) { 18 | defer assertRecoveryPanic(t) 19 | config := configDefault() 20 | 21 | assert.Equal(t, "", config.SymmetricKey) 22 | } 23 | 24 | func Test_Config_Invalid_SymmetricKey(t *testing.T) { 25 | defer assertRecoveryPanic(t) 26 | config := configDefault() 27 | 28 | assert.Equal(t, symmetricKey+symmetricKey, config.SymmetricKey) 29 | } 30 | 31 | func Test_ConfigDefault(t *testing.T) { 32 | config := configDefault(Config{ 33 | SymmetricKey: []byte(symmetricKey), 34 | }) 35 | 36 | assert.Equal(t, extractors.SourceAuthHeader, config.Extractor.Source) 37 | assert.Equal(t, fiber.HeaderAuthorization, config.Extractor.Key) 38 | assert.Equal(t, "Bearer", config.Extractor.AuthScheme) 39 | assert.Empty(t, config.Extractor.Chain) 40 | 41 | assert.NotNil(t, config.Validate) 42 | } 43 | 44 | func Test_ConfigCustomLookup(t *testing.T) { 45 | config := configDefault(Config{ 46 | SymmetricKey: []byte(symmetricKey), 47 | Extractor: extractors.FromHeader("Custom-Header"), 48 | }) 49 | assert.Equal(t, extractors.SourceHeader, config.Extractor.Source) 50 | assert.Equal(t, "Custom-Header", config.Extractor.Key) 51 | assert.Equal(t, "", config.Extractor.AuthScheme) 52 | 53 | config = configDefault(Config{ 54 | SymmetricKey: []byte(symmetricKey), 55 | Extractor: extractors.FromQuery("token"), 56 | }) 57 | assert.Equal(t, extractors.SourceQuery, config.Extractor.Source) 58 | assert.Equal(t, "token", config.Extractor.Key) 59 | assert.Equal(t, "", config.Extractor.AuthScheme) 60 | } 61 | -------------------------------------------------------------------------------- /paseto/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/paseto 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/google/uuid v1.6.0 8 | github.com/o1egl/paseto v1.0.0 9 | github.com/stretchr/testify v1.11.1 10 | golang.org/x/crypto v0.42.0 11 | ) 12 | 13 | require ( 14 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 15 | github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect 16 | github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect 17 | github.com/andybalholm/brotli v1.2.0 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/gofiber/schema v1.6.0 // indirect 20 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 21 | github.com/klauspost/compress v1.18.0 // indirect 22 | github.com/mattn/go-colorable v0.1.14 // indirect 23 | github.com/mattn/go-isatty v0.0.20 // indirect 24 | github.com/philhofer/fwd v1.2.0 // indirect 25 | github.com/pkg/errors v0.9.1 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/tinylib/msgp v1.4.0 // indirect 28 | github.com/valyala/bytebufferpool v1.0.0 // indirect 29 | github.com/valyala/fasthttp v1.66.0 // indirect 30 | golang.org/x/net v0.44.0 // indirect 31 | golang.org/x/sys v0.36.0 // indirect 32 | golang.org/x/text v0.29.0 // indirect 33 | gopkg.in/yaml.v3 v3.0.1 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /paseto/helpers.go: -------------------------------------------------------------------------------- 1 | package pasetoware 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "errors" 6 | "time" 7 | 8 | "github.com/o1egl/paseto" 9 | ) 10 | 11 | type TokenPurpose int 12 | 13 | const ( 14 | PurposeLocal TokenPurpose = iota 15 | PurposePublic 16 | ) 17 | 18 | var ( 19 | ErrExpiredToken = errors.New("token has expired") 20 | ErrMissingToken = errors.New("missing PASETO token") 21 | ErrDataUnmarshal = errors.New("can't unmarshal token data to Payload type") 22 | pasetoObject = paseto.NewV2() 23 | ) 24 | 25 | // PayloadValidator Function that receives the decrypted payload and returns an interface and an error 26 | // that's a result of validation logic 27 | type PayloadValidator func(decrypted []byte) (interface{}, error) 28 | 29 | // PayloadCreator Signature of a function that generates a payload token 30 | type PayloadCreator func(key []byte, dataInfo string, duration time.Duration, purpose TokenPurpose) (string, error) 31 | 32 | // Public helper functions 33 | 34 | // CreateToken Create a new Token Payload that will be stored in PASETO 35 | func CreateToken(key []byte, dataInfo string, duration time.Duration, purpose TokenPurpose) (string, error) { 36 | payload, err := NewPayload(dataInfo, duration) 37 | if err != nil { 38 | return "", err 39 | } 40 | 41 | switch purpose { 42 | case PurposeLocal: 43 | return pasetoObject.Encrypt(key, payload, nil) 44 | case PurposePublic: 45 | return pasetoObject.Sign(ed25519.PrivateKey(key), payload, nil) 46 | default: 47 | return pasetoObject.Encrypt(key, payload, nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /paseto/paseto.go: -------------------------------------------------------------------------------- 1 | package pasetoware 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gofiber/fiber/v3" 7 | "github.com/gofiber/fiber/v3/extractors" 8 | ) 9 | 10 | // The contextKey type is unexported to prevent collisions with context keys defined in 11 | // other packages. 12 | type contextKey int 13 | 14 | // The following contextKey values are defined to store values in context. 15 | const ( 16 | payloadKey contextKey = iota 17 | ) 18 | 19 | // New PASETO middleware, returns a handler that takes a token in selected lookup param and in case token is valid 20 | // it saves the decrypted token on ctx.Locals, take a look on Config to know more configuration options 21 | func New(authConfigs ...Config) fiber.Handler { 22 | // Set default authConfig 23 | config := configDefault(authConfigs...) 24 | 25 | // Return middleware handler 26 | return func(c fiber.Ctx) error { 27 | // Filter request to skip middleware 28 | if config.Next != nil && config.Next(c) { 29 | return c.Next() 30 | } 31 | 32 | token, err := config.Extractor.Extract(c) 33 | if err != nil { 34 | if errors.Is(err, extractors.ErrNotFound) { 35 | return config.ErrorHandler(c, ErrMissingToken) 36 | } 37 | return config.ErrorHandler(c, err) 38 | } 39 | 40 | var outData []byte 41 | 42 | if config.SymmetricKey != nil { 43 | if err := pasetoObject.Decrypt(token, config.SymmetricKey, &outData, nil); err != nil { 44 | return config.ErrorHandler(c, err) 45 | } 46 | } else { 47 | if err := pasetoObject.Verify(token, config.PublicKey, &outData, nil); err != nil { 48 | return config.ErrorHandler(c, err) 49 | } 50 | } 51 | 52 | payload, err := config.Validate(outData) 53 | if err == nil { 54 | // Store user information from token into context. 55 | c.Locals(payloadKey, payload) 56 | 57 | return config.SuccessHandler(c) 58 | } 59 | 60 | return config.ErrorHandler(c, err) 61 | } 62 | } 63 | 64 | // FromContext returns the payload from the context. 65 | func FromContext(c fiber.Ctx) interface{} { 66 | return c.Locals(payloadKey) 67 | } 68 | -------------------------------------------------------------------------------- /paseto/payload.go: -------------------------------------------------------------------------------- 1 | package pasetoware 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/uuid" 7 | "github.com/o1egl/paseto" 8 | ) 9 | 10 | const ( 11 | pasetoTokenAudience = "gofiber.gophers" 12 | pasetoTokenSubject = "user-token" 13 | pasetoTokenField = "data" 14 | ) 15 | 16 | // NewPayload generates a new paseto.JSONToken and returns it and a error that can be caused by uuid 17 | func NewPayload(userToken string, duration time.Duration) (*paseto.JSONToken, error) { 18 | tokenID, err := uuid.NewRandom() 19 | if err != nil { 20 | return nil, err 21 | } 22 | timeNow := time.Now() 23 | payload := &paseto.JSONToken{ 24 | Audience: pasetoTokenAudience, 25 | Jti: tokenID.String(), 26 | Subject: pasetoTokenSubject, 27 | IssuedAt: timeNow, 28 | Expiration: timeNow.Add(duration), 29 | NotBefore: timeNow, 30 | } 31 | 32 | payload.Set(pasetoTokenField, userToken) 33 | return payload, nil 34 | } 35 | -------------------------------------------------------------------------------- /socketio/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/socketio 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/fasthttp/websocket v1.5.10 7 | github.com/gofiber/contrib/websocket v1.3.4 8 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 9 | github.com/google/uuid v1.6.0 10 | github.com/stretchr/testify v1.11.1 11 | github.com/valyala/fasthttp v1.65.0 12 | ) 13 | 14 | require ( 15 | github.com/andybalholm/brotli v1.2.0 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/gofiber/fiber/v2 v2.52.6 // indirect 18 | github.com/gofiber/schema v1.6.0 // indirect 19 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 20 | github.com/klauspost/compress v1.18.0 // indirect 21 | github.com/mattn/go-colorable v0.1.14 // indirect 22 | github.com/mattn/go-isatty v0.0.20 // indirect 23 | github.com/mattn/go-runewidth v0.0.16 // indirect 24 | github.com/philhofer/fwd v1.2.0 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/rivo/uniseg v0.4.4 // indirect 27 | github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect 28 | github.com/stretchr/objx v0.5.2 // indirect 29 | github.com/tinylib/msgp v1.4.0 // indirect 30 | github.com/valyala/bytebufferpool v1.0.0 // indirect 31 | golang.org/x/crypto v0.42.0 // indirect 32 | golang.org/x/net v0.44.0 // indirect 33 | golang.org/x/sys v0.36.0 // indirect 34 | golang.org/x/text v0.29.0 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /swagger/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: swagger 3 | title: Swagger 4 | --- 5 | 6 | # Swagger 7 | 8 | ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=swagger*) 9 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 10 | ![Test](https://github.com/gofiber/contrib/workflows/Test%20swagger/badge.svg) 11 | 12 | Swagger middleware for [Fiber](https://github.com/gofiber/fiber). The middleware handles Swagger UI. 13 | 14 | **Note: Requires Go 1.25 and above** 15 | 16 | ### Table of Contents 17 | - [Signatures](#signatures) 18 | - [Installation](#installation) 19 | - [Examples](#examples) 20 | - [Config](#config) 21 | - [Default Config](#default-config) 22 | 23 | ### Signatures 24 | ```go 25 | func New(config ...swagger.Config) fiber.Handler 26 | ``` 27 | 28 | ### Installation 29 | Swagger is tested on the latests [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: 30 | ```bash 31 | go mod init github.com// 32 | ``` 33 | And then install the swagger middleware: 34 | ```bash 35 | go get github.com/gofiber/contrib/swagger 36 | ``` 37 | 38 | ### Examples 39 | Import the middleware package 40 | ```go 41 | import ( 42 | "github.com/gofiber/fiber/v3" 43 | "github.com/gofiber/contrib/swagger" 44 | ) 45 | ``` 46 | 47 | Using the default config: 48 | ```go 49 | app.Use(swagger.New()) 50 | ``` 51 | 52 | Using a custom config: 53 | ```go 54 | cfg := swagger.Config{ 55 | BasePath: "/", 56 | FilePath: "./docs/swagger.json", 57 | Path: "swagger", 58 | Title: "Swagger API Docs", 59 | } 60 | 61 | app.Use(swagger.New(cfg)) 62 | ``` 63 | 64 | Use program data for Swagger content: 65 | ```go 66 | cfg := swagger.Config{ 67 | BasePath: "/", 68 | FilePath: "./docs/swagger.json", 69 | FileContent: mySwaggerByteSlice, 70 | Path: "swagger", 71 | Title: "Swagger API Docs", 72 | } 73 | 74 | app.Use(swagger.New(cfg)) 75 | ``` 76 | 77 | Using multiple instances of Swagger: 78 | ```go 79 | // Create Swagger middleware for v1 80 | // 81 | // Swagger will be available at: /api/v1/docs 82 | app.Use(swagger.New(swagger.Config{ 83 | BasePath: "/api/v1/", 84 | FilePath: "./docs/v1/swagger.json", 85 | Path: "docs", 86 | })) 87 | 88 | // Create Swagger middleware for v2 89 | // 90 | // Swagger will be available at: /api/v2/docs 91 | app.Use(swagger.New(swagger.Config{ 92 | BasePath: "/api/v2/", 93 | FilePath: "./docs/v2/swagger.json", 94 | Path: "docs", 95 | })) 96 | ``` 97 | 98 | ### Config 99 | ```go 100 | type Config struct { 101 | // Next defines a function to skip this middleware when returned true. 102 | // 103 | // Optional. Default: nil 104 | Next func(c *fiber.Ctx) bool 105 | 106 | // BasePath for the UI path 107 | // 108 | // Optional. Default: / 109 | BasePath string 110 | 111 | // FilePath for the swagger.json or swagger.yaml file 112 | // 113 | // Optional. Default: ./swagger.json 114 | FilePath string 115 | 116 | // FileContent for the content of the swagger.json or swagger.yaml file. 117 | // If provided, FilePath will not be read. 118 | // 119 | // Optional. Default: nil 120 | FileContent []byte 121 | 122 | // Path combines with BasePath for the full UI path 123 | // 124 | // Optional. Default: docs 125 | Path string 126 | 127 | // Title for the documentation site 128 | // 129 | // Optional. Default: Fiber API documentation 130 | Title string 131 | 132 | // CacheAge defines the max-age for the Cache-Control header in seconds. 133 | // 134 | // Optional. Default: 3600 (1 hour) 135 | CacheAge int 136 | } 137 | ``` 138 | 139 | ### Default Config 140 | ```go 141 | var ConfigDefault = Config{ 142 | Next: nil, 143 | BasePath: "/", 144 | FilePath: "./swagger.json", 145 | Path: "docs", 146 | Title: "Fiber API documentation", 147 | CacheAge: 3600, // Default to 1 hour 148 | } 149 | ``` 150 | -------------------------------------------------------------------------------- /swagger/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/swagger 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/go-openapi/runtime v0.29.0 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | github.com/stretchr/testify v1.11.1 9 | gopkg.in/yaml.v2 v2.4.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.2.0 // indirect 14 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/go-openapi/analysis v0.24.0 // indirect 17 | github.com/go-openapi/errors v0.22.3 // indirect 18 | github.com/go-openapi/jsonpointer v0.22.1 // indirect 19 | github.com/go-openapi/jsonreference v0.21.2 // indirect 20 | github.com/go-openapi/loads v0.23.1 // indirect 21 | github.com/go-openapi/spec v0.22.0 // indirect 22 | github.com/go-openapi/strfmt v0.24.0 // indirect 23 | github.com/go-openapi/swag/conv v0.25.1 // indirect 24 | github.com/go-openapi/swag/fileutils v0.25.1 // indirect 25 | github.com/go-openapi/swag/jsonname v0.25.1 // indirect 26 | github.com/go-openapi/swag/jsonutils v0.25.1 // indirect 27 | github.com/go-openapi/swag/loading v0.25.1 // indirect 28 | github.com/go-openapi/swag/mangling v0.25.1 // indirect 29 | github.com/go-openapi/swag/stringutils v0.25.1 // indirect 30 | github.com/go-openapi/swag/typeutils v0.25.1 // indirect 31 | github.com/go-openapi/swag/yamlutils v0.25.1 // indirect 32 | github.com/go-openapi/validate v0.25.0 // indirect 33 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 34 | github.com/gofiber/schema v1.6.0 // indirect 35 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 36 | github.com/google/uuid v1.6.0 // indirect 37 | github.com/klauspost/compress v1.18.0 // indirect 38 | github.com/mattn/go-colorable v0.1.14 // indirect 39 | github.com/mattn/go-isatty v0.0.20 // indirect 40 | github.com/oklog/ulid v1.3.1 // indirect 41 | github.com/philhofer/fwd v1.2.0 // indirect 42 | github.com/pmezard/go-difflib v1.0.0 // indirect 43 | github.com/tinylib/msgp v1.4.0 // indirect 44 | github.com/valyala/bytebufferpool v1.0.0 // indirect 45 | github.com/valyala/fasthttp v1.65.0 // indirect 46 | go.mongodb.org/mongo-driver v1.17.4 // indirect 47 | go.yaml.in/yaml/v3 v3.0.4 // indirect 48 | golang.org/x/crypto v0.42.0 // indirect 49 | golang.org/x/net v0.44.0 // indirect 50 | golang.org/x/sync v0.17.0 // indirect 51 | golang.org/x/sys v0.36.0 // indirect 52 | golang.org/x/text v0.29.0 // indirect 53 | gopkg.in/yaml.v3 v3.0.1 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /swagger/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumes": [ 3 | "application/json" 4 | ], 5 | "produces": [ 6 | "application/json" 7 | ], 8 | "schemes": [ 9 | "http" 10 | ], 11 | "swagger": "2.0", 12 | "info": { 13 | "description": "Documentation for TestApi", 14 | "title": "TestApi", 15 | "version": "1.0.0" 16 | }, 17 | "basePath": "/" 18 | } -------------------------------------------------------------------------------- /swagger/swagger.yaml: -------------------------------------------------------------------------------- 1 | consumes: 2 | - application/json 3 | produces: 4 | - application/json 5 | schemes: 6 | - http 7 | swagger: "2.0" 8 | info: 9 | description: "Documentation for TestApi" 10 | title: "TestApi" 11 | version: "1.0.0" 12 | basePath: "/" -------------------------------------------------------------------------------- /swagger/swagger_missing.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumes": [ 3 | "application/json" 4 | ], 5 | "produces": [ 6 | "application/json" 7 | ], 8 | "schemes": [ 9 | "http" 10 | ], 11 | "swagger": 12 | "info": { 13 | "description": "Documentation for TestApi", 14 | "title": "TestApi", 15 | "version": "1.0.0" 16 | }, 17 | "basePath": "/" 18 | } -------------------------------------------------------------------------------- /testcontainers/config.go: -------------------------------------------------------------------------------- 1 | package testcontainers 2 | 3 | import ( 4 | "context" 5 | 6 | tc "github.com/testcontainers/testcontainers-go" 7 | ) 8 | 9 | // Config contains the configuration for a container service. 10 | type Config[T tc.Container] struct { 11 | // ServiceKey is the key used to identify the service in the Fiber app's state. 12 | ServiceKey string 13 | 14 | // Image is the image name to use for the container. 15 | Image string 16 | 17 | // Run is the function to use to run the container. 18 | // It's usually the Run function from the testcontainers-go module, like redis.Run or postgres.Run, 19 | // although it could be the generic [tc.Run] function from the testcontainers-go package. 20 | Run func(ctx context.Context, img string, opts ...tc.ContainerCustomizer) (T, error) 21 | 22 | // Options are the functional options to pass to the [tc.Run] function. This argument is optional. 23 | // You can find the available options in the [testcontainers website]. 24 | // 25 | // [testcontainers website]: https://golang.tc.org/features/creating_container/#customizing-the-container 26 | Options []tc.ContainerCustomizer 27 | } 28 | 29 | // NewModuleConfig creates a new container service config for a module. 30 | // 31 | // - The serviceKey is the key used to identify the service in the Fiber app's state. 32 | // - The img is the image name to use for the container. 33 | // - The run is the function to use to run the container. It's usually the Run function from the module, like [redis.Run] or [postgres.Run]. 34 | // - The opts are the functional options to pass to the run function. This argument is optional. 35 | func NewModuleConfig[T tc.Container]( 36 | serviceKey string, 37 | img string, 38 | run func(ctx context.Context, img string, opts ...tc.ContainerCustomizer) (T, error), 39 | opts ...tc.ContainerCustomizer, 40 | ) Config[T] { 41 | 42 | return Config[T]{ 43 | ServiceKey: serviceKey, 44 | Image: img, 45 | Run: run, 46 | Options: opts, 47 | } 48 | } 49 | 50 | // NewContainerConfig creates a new container service config for a generic container type, 51 | // not created by a Testcontainers module. So this function best used in combination with 52 | // the [AddService] function to add a custom container to the Fiber app's state. 53 | // 54 | // - The serviceKey is the key used to identify the service in the Fiber app's state. 55 | // - The img is the image name to use for the container. 56 | // - The opts are the functional options to pass to the [tc.Run] function. This argument is optional. 57 | // 58 | // This function uses the [tc.Run] function as the run function. 59 | func NewContainerConfig(serviceKey string, img string, opts ...tc.ContainerCustomizer) Config[*tc.DockerContainer] { 60 | return NewModuleConfig(serviceKey, img, tc.Run, opts...) 61 | } 62 | -------------------------------------------------------------------------------- /testcontainers/examples_test.go: -------------------------------------------------------------------------------- 1 | package testcontainers_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/gofiber/fiber/v3" 8 | 9 | "github.com/gofiber/contrib/testcontainers" 10 | tc "github.com/testcontainers/testcontainers-go" 11 | "github.com/testcontainers/testcontainers-go/modules/postgres" 12 | "github.com/testcontainers/testcontainers-go/modules/redis" 13 | ) 14 | 15 | func ExampleAddService_fromContainer() { 16 | cfg := &fiber.Config{} 17 | 18 | // Define the base key for the generic service. 19 | // The service returned by the [testcontainers.AddService] function, 20 | // using the [ContainerService.Key] method, 21 | // concatenates the base key with the "using testcontainers-go" suffix. 22 | const ( 23 | nginxKey = "nginx-generic" 24 | ) 25 | 26 | // Adding a generic container, directly from the testcontainers-go package. 27 | containerConfig := testcontainers.NewContainerConfig(nginxKey, "nginx:latest", tc.WithExposedPorts("80/tcp")) 28 | 29 | nginxSrv, err := testcontainers.AddService(cfg, containerConfig) 30 | if err != nil { 31 | log.Println("error adding nginx generic:", err) 32 | return 33 | } 34 | 35 | app := fiber.New(*cfg) 36 | fmt.Println(app.State().ServicesLen()) 37 | 38 | srvs := app.State().Services() 39 | fmt.Println(len(srvs)) 40 | 41 | nginxCtr := fiber.MustGetService[*testcontainers.ContainerService[*tc.DockerContainer]](app.State(), nginxSrv.Key()) 42 | 43 | fmt.Println(nginxCtr.String()) 44 | 45 | // Output: 46 | // 1 47 | // 1 48 | // nginx-generic (using testcontainers-go) 49 | } 50 | 51 | func ExampleAddService_fromModule() { 52 | cfg := &fiber.Config{} 53 | 54 | // Define the base keys for the module services. 55 | // The service returned by the [testcontainers.AddService] function, 56 | // using the [ContainerService.Key] method, 57 | // concatenates the base key with the "using testcontainers-go" suffix. 58 | const ( 59 | redisKey = "redis-module" 60 | postgresKey = "postgres-module" 61 | ) 62 | 63 | // Adding containers coming from the testcontainers-go modules, 64 | // in this case, a Redis and a Postgres container. 65 | 66 | redisModuleConfig := testcontainers.NewModuleConfig(redisKey, "redis:latest", redis.Run) 67 | redisSrv, err := testcontainers.AddService(cfg, redisModuleConfig) 68 | if err != nil { 69 | log.Println("error adding redis module:", err) 70 | return 71 | } 72 | 73 | postgresModuleConfig := testcontainers.NewModuleConfig(postgresKey, "postgres:latest", postgres.Run) 74 | postgresSrv, err := testcontainers.AddService(cfg, postgresModuleConfig) 75 | if err != nil { 76 | log.Println("error adding postgres module:", err) 77 | return 78 | } 79 | 80 | // Create a new Fiber app, using the provided configuration. 81 | app := fiber.New(*cfg) 82 | 83 | // Verify the number of services in the app's state. 84 | fmt.Println(app.State().ServicesLen()) 85 | 86 | // Retrieve all services from the app's state. 87 | // This returns a slice of all the services registered in the app's state. 88 | srvs := app.State().Services() 89 | fmt.Println(len(srvs)) 90 | 91 | // Retrieve the Redis container from the app's state using the key returned by the [ContainerService.Key] method. 92 | redisCtr := fiber.MustGetService[*testcontainers.ContainerService[*redis.RedisContainer]](app.State(), redisSrv.Key()) 93 | 94 | // Retrieve the Postgres container from the app's state using the key returned by the [ContainerService.Key] method. 95 | postgresCtr := fiber.MustGetService[*testcontainers.ContainerService[*postgres.PostgresContainer]](app.State(), postgresSrv.Key()) 96 | 97 | // Verify the string representation of the Redis and Postgres containers. 98 | fmt.Println(redisCtr.String()) 99 | fmt.Println(postgresCtr.String()) 100 | 101 | // Output: 102 | // 2 103 | // 2 104 | // redis-module (using testcontainers-go) 105 | // postgres-module (using testcontainers-go) 106 | } 107 | -------------------------------------------------------------------------------- /testcontainers/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/testcontainers 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 7 | github.com/testcontainers/testcontainers-go v0.39.0 8 | ) 9 | 10 | // Test-time dependencies 11 | require ( 12 | github.com/stretchr/testify v1.11.1 13 | github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 14 | github.com/testcontainers/testcontainers-go/modules/redis v0.38.0 15 | ) 16 | 17 | require ( 18 | dario.cat/mergo v1.0.2 // indirect 19 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 20 | github.com/Microsoft/go-winio v0.6.2 // indirect 21 | github.com/andybalholm/brotli v1.2.0 // indirect 22 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 23 | github.com/containerd/errdefs v1.0.0 // indirect 24 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 25 | github.com/containerd/log v0.1.0 // indirect 26 | github.com/containerd/platforms v0.2.1 // indirect 27 | github.com/cpuguy83/dockercfg v0.3.2 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/distribution/reference v0.6.0 // indirect 30 | github.com/docker/docker v28.3.3+incompatible // indirect 31 | github.com/docker/go-connections v0.6.0 // indirect 32 | github.com/docker/go-units v0.5.0 // indirect 33 | github.com/ebitengine/purego v0.8.4 // indirect 34 | github.com/felixge/httpsnoop v1.0.4 // indirect 35 | github.com/go-logr/logr v1.4.2 // indirect 36 | github.com/go-logr/stdr v1.2.2 // indirect 37 | github.com/go-ole/go-ole v1.2.6 // indirect 38 | github.com/gofiber/schema v1.6.0 // indirect 39 | github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect 40 | github.com/gogo/protobuf v1.3.2 // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/klauspost/compress v1.18.0 // indirect 43 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 44 | github.com/magiconair/properties v1.8.10 // indirect 45 | github.com/mattn/go-colorable v0.1.14 // indirect 46 | github.com/mattn/go-isatty v0.0.20 // indirect 47 | github.com/mdelapenya/tlscert v0.2.0 // indirect 48 | github.com/moby/docker-image-spec v1.3.1 // indirect 49 | github.com/moby/go-archive v0.1.0 // indirect 50 | github.com/moby/patternmatcher v0.6.0 // indirect 51 | github.com/moby/sys/sequential v0.6.0 // indirect 52 | github.com/moby/sys/user v0.4.0 // indirect 53 | github.com/moby/sys/userns v0.1.0 // indirect 54 | github.com/moby/term v0.5.0 // indirect 55 | github.com/morikuni/aec v1.0.0 // indirect 56 | github.com/opencontainers/go-digest v1.0.0 // indirect 57 | github.com/opencontainers/image-spec v1.1.1 // indirect 58 | github.com/philhofer/fwd v1.2.0 // indirect 59 | github.com/pkg/errors v0.9.1 // indirect 60 | github.com/pmezard/go-difflib v1.0.0 // indirect 61 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 62 | github.com/shirou/gopsutil/v4 v4.25.6 // indirect 63 | github.com/sirupsen/logrus v1.9.3 // indirect 64 | github.com/tinylib/msgp v1.4.0 // indirect 65 | github.com/tklauser/go-sysconf v0.3.12 // indirect 66 | github.com/tklauser/numcpus v0.6.1 // indirect 67 | github.com/valyala/bytebufferpool v1.0.0 // indirect 68 | github.com/valyala/fasthttp v1.65.0 // indirect 69 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 70 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 71 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 72 | go.opentelemetry.io/otel v1.35.0 // indirect 73 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect 74 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 75 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect 76 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 77 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 78 | golang.org/x/crypto v0.42.0 // indirect 79 | golang.org/x/net v0.44.0 // indirect 80 | golang.org/x/sys v0.36.0 // indirect 81 | golang.org/x/text v0.29.0 // indirect 82 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect 83 | google.golang.org/protobuf v1.36.6 // indirect 84 | gopkg.in/yaml.v3 v3.0.1 // indirect 85 | ) 86 | -------------------------------------------------------------------------------- /testcontainers/testcontainers_unit_test.go: -------------------------------------------------------------------------------- 1 | package testcontainers 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gofiber/fiber/v3" 8 | "github.com/stretchr/testify/require" 9 | "github.com/testcontainers/testcontainers-go/modules/redis" 10 | ) 11 | 12 | func Test_buildKey(t *testing.T) { 13 | t.Run("no-suffix", func(t *testing.T) { 14 | key := "test" 15 | got := buildKey(key) 16 | require.Equal(t, key+serviceSuffix, got) 17 | }) 18 | 19 | t.Run("with-suffix", func(t *testing.T) { 20 | key := "test-suffix" + serviceSuffix 21 | got := buildKey(key) 22 | require.Equal(t, key, got) 23 | }) 24 | } 25 | 26 | func Test_ContainersService_Start(t *testing.T) { 27 | t.Run("twice-error", func(t *testing.T) { 28 | cfg := fiber.Config{} 29 | 30 | moduleConfig := NewModuleConfig("redis-module-twice-error", "redis:alpine", redis.Run) 31 | 32 | srv, err := AddService(&cfg, moduleConfig) 33 | require.NoError(t, err) 34 | 35 | opts1 := srv.opts 36 | 37 | require.NoError(t, srv.Start(context.Background())) 38 | t.Cleanup(func() { 39 | require.NoError(t, srv.Terminate(context.Background())) 40 | }) 41 | 42 | require.Error(t, srv.Start(context.Background())) 43 | 44 | // verify that the opts are not modified 45 | opts2 := srv.opts 46 | require.Equal(t, opts1, opts2) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /websocket/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/contrib/websocket 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/fasthttp/websocket v1.5.12 7 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 8 | github.com/gofiber/utils/v2 v2.0.0-rc.1 9 | github.com/stretchr/testify v1.11.1 10 | github.com/valyala/fasthttp v1.66.0 11 | ) 12 | 13 | require ( 14 | github.com/andybalholm/brotli v1.2.0 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/gofiber/schema v1.6.0 // indirect 17 | github.com/google/uuid v1.6.0 // indirect 18 | github.com/klauspost/compress v1.18.0 // indirect 19 | github.com/mattn/go-colorable v0.1.14 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/philhofer/fwd v1.2.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 // indirect 24 | github.com/tinylib/msgp v1.4.0 // indirect 25 | github.com/valyala/bytebufferpool v1.0.0 // indirect 26 | golang.org/x/crypto v0.42.0 // indirect 27 | golang.org/x/net v0.44.0 // indirect 28 | golang.org/x/sys v0.36.0 // indirect 29 | golang.org/x/text v0.29.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /websocket/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 2 | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE= 6 | github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= 7 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 8 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 9 | github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0= 10 | github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg= 11 | github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= 12 | github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= 13 | github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s= 14 | github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E= 15 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 16 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 17 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 18 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 19 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 20 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 21 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 22 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 23 | github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= 24 | github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 h1:McifyVxygw1d67y6vxUqls2D46J8W9nrki9c8c0eVvE= 28 | github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761/go.mod h1:Vi9gvHvTw4yCUHIznFl5TPULS7aXwgaTByGeBY75Wko= 29 | github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo= 30 | github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= 31 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 32 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 33 | github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= 34 | github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= 35 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 36 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 37 | github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU= 38 | github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= 39 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 40 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 41 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 42 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 43 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 44 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 45 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 46 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 47 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 49 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 50 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 51 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 55 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 56 | --------------------------------------------------------------------------------