mirror of
https://github.com/gin-gonic/gin.git
synced 2026-01-10 00:07:04 +08:00
Merge branch 'master' into afiune/add-StringHTML
This commit is contained in:
commit
2f4c44627b
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,7 +1,7 @@
|
|||||||
- With pull requests:
|
- With pull requests:
|
||||||
- Open your pull request against `master`
|
- Open your pull request against `master`
|
||||||
- Your pull request should have no more than two commits, if not you should squash them.
|
- Your pull request should have no more than two commits, if not you should squash them.
|
||||||
- It should pass all tests in the available continuous integration systems such as TravisCI.
|
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
|
||||||
- You should add/modify tests to cover your proposed code changes.
|
- You should add/modify tests to cover your proposed code changes.
|
||||||
- If your pull request contains a new feature, please document it on the README.
|
- If your pull request contains a new feature, please document it on the README.
|
||||||
|
|
||||||
|
|||||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
49
.github/workflows/codeql.yml
vendored
Normal file
49
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 17 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Override automatic language detection by changing the below list
|
||||||
|
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||||
|
# TODO: Enable for javascript later
|
||||||
|
language: [ 'go']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
84
.github/workflows/gin.yml
vendored
Normal file
84
.github/workflows/gin.yml
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
name: Run Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Setup go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3.2.0
|
||||||
|
with:
|
||||||
|
version: v1.45.0
|
||||||
|
args: --verbose
|
||||||
|
test:
|
||||||
|
needs: lint
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
go: [1.15, 1.16, 1.17, 1.18]
|
||||||
|
test-tags: ['', nomsgpack]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
go-build: ~/.cache/go-build
|
||||||
|
- os: macos-latest
|
||||||
|
go-build: ~/Library/Caches/go-build
|
||||||
|
name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
GO111MODULE: on
|
||||||
|
TESTTAGS: ${{ matrix.test-tags }}
|
||||||
|
GOPROXY: https://proxy.golang.org
|
||||||
|
steps:
|
||||||
|
- name: Set up Go ${{ matrix.go }}
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ matrix.go-build }}
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
||||||
|
notification-gitter:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Notification failure message
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
|
||||||
|
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||||
|
- name: Notification success message
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
|
||||||
|
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||||
34
.github/workflows/goreleaser.yml
vendored
Normal file
34
.github/workflows/goreleaser.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: Goreleaser
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
-
|
||||||
|
name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.17
|
||||||
|
-
|
||||||
|
name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v3
|
||||||
|
with:
|
||||||
|
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||||
|
distribution: goreleaser
|
||||||
|
version: latest
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
39
.golangci.yml
Normal file
39
.golangci.yml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- depguard
|
||||||
|
- dogsled
|
||||||
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
|
- errorlint
|
||||||
|
- exportloopref
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosec
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- nolintlint
|
||||||
|
- revive
|
||||||
|
- wastedassign
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- structcheck
|
||||||
|
- unused
|
||||||
|
text: "`data` is unused"
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: "SA1019:"
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "var-naming:"
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "exported:"
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gosec # security is not make sense in tests
|
||||||
57
.goreleaser.yaml
Normal file
57
.goreleaser.yaml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
project_name: gin
|
||||||
|
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
# If true, skip the build.
|
||||||
|
# Useful for library projects.
|
||||||
|
# Default is false
|
||||||
|
skip: true
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
# Set it to true if you wish to skip the changelog generation.
|
||||||
|
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||||
|
skip: false
|
||||||
|
|
||||||
|
# Changelog generation implementation to use.
|
||||||
|
#
|
||||||
|
# Valid options are:
|
||||||
|
# - `git`: uses `git log`;
|
||||||
|
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
|
||||||
|
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
|
||||||
|
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||||
|
#
|
||||||
|
# Defaults to `git`.
|
||||||
|
use: git
|
||||||
|
|
||||||
|
# Sorts the changelog by the commit's messages.
|
||||||
|
# Could either be asc, desc or empty
|
||||||
|
# Default is empty
|
||||||
|
sort: asc
|
||||||
|
|
||||||
|
# Group commits messages by given regex and title.
|
||||||
|
# Order value defines the order of the groups.
|
||||||
|
# Proving no regex means all commits will be grouped under the default group.
|
||||||
|
# Groups are disabled when using github-native, as it already groups things by itself.
|
||||||
|
#
|
||||||
|
# Default is no groups.
|
||||||
|
groups:
|
||||||
|
- title: Features
|
||||||
|
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||||
|
order: 0
|
||||||
|
- title: 'Bug fixes'
|
||||||
|
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||||
|
order: 1
|
||||||
|
- title: 'Enhancements'
|
||||||
|
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||||
|
order: 2
|
||||||
|
- title: Others
|
||||||
|
order: 999
|
||||||
|
|
||||||
|
filters:
|
||||||
|
# Commit messages matching the regexp listed here will be removed from
|
||||||
|
# the changelog
|
||||||
|
# Default is empty
|
||||||
|
exclude:
|
||||||
|
- '^docs'
|
||||||
|
- 'CICD'
|
||||||
|
- typo
|
||||||
50
.travis.yml
50
.travis.yml
@ -1,50 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- go: 1.12.x
|
|
||||||
env: GO111MODULE=on
|
|
||||||
- go: 1.13.x
|
|
||||||
- go: 1.13.x
|
|
||||||
env:
|
|
||||||
- TESTTAGS=nomsgpack
|
|
||||||
- go: 1.14.x
|
|
||||||
- go: 1.14.x
|
|
||||||
env:
|
|
||||||
- TESTTAGS=nomsgpack
|
|
||||||
- go: 1.15.x
|
|
||||||
- go: 1.15.x
|
|
||||||
env:
|
|
||||||
- TESTTAGS=nomsgpack
|
|
||||||
- go: master
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 10
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
|
|
||||||
|
|
||||||
install:
|
|
||||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
|
|
||||||
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
|
|
||||||
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
|
|
||||||
|
|
||||||
go_import_path: github.com/gin-gonic/gin
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make vet
|
|
||||||
- make fmt-check
|
|
||||||
- make misspell-check
|
|
||||||
- make test
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
|
||||||
on_success: change # options: [always|never|change] default: always
|
|
||||||
on_failure: always # options: [always|never|change] default: always
|
|
||||||
on_start: false # default: false
|
|
||||||
615
AUTHORS.md
615
AUTHORS.md
@ -2,230 +2,405 @@ List of all the awesome people working to make Gin the best Web Framework in Go.
|
|||||||
|
|
||||||
## gin 1.x series authors
|
## gin 1.x series authors
|
||||||
|
|
||||||
**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho)
|
**Gin Core Team:** Bo-Yi Wu (@appleboy), thinkerou (@thinkerou), Javier Provecho (@javierprovecho)
|
||||||
|
|
||||||
## gin 0.x series authors
|
## gin 0.x series authors
|
||||||
|
|
||||||
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
People and companies, who have contributed, in alphabetical order.
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
**@858806258 (杰哥)**
|
- 178inaba <178inaba@users.noreply.github.com>
|
||||||
- Fix typo in example
|
- A. F <hello@clivern.com>
|
||||||
|
- ABHISHEK SONI <abhishek.rocks26@gmail.com>
|
||||||
|
- Abhishek Chanda <achanda@users.noreply.github.com>
|
||||||
**@achedeuzot (Klemen Sever)**
|
- Abner Chen <houjunchen@gmail.com>
|
||||||
- Fix newline debug printing
|
- AcoNCodes <acongame@gmail.com>
|
||||||
|
- Adam Dratwinski <adam.dratwinski@gmail.com>
|
||||||
|
- Adam Mckaig <adam.mckaig@gmail.com>
|
||||||
**@adammck (Adam Mckaig)**
|
- Adam Zielinski <MusicAdam@users.noreply.github.com>
|
||||||
- Add MIT license
|
- Adonis <donileo@gmail.com>
|
||||||
|
- Alan Wang <azzwacb9001@126.com>
|
||||||
|
- Albin Gilles <gilles.albin@gmail.com>
|
||||||
**@AlexanderChen1989 (Alexander)**
|
- Aleksandr Didenko <aa.didenko@yandex.ru>
|
||||||
- Typos in README
|
- Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
|
||||||
|
- Alex <AWulkan@users.noreply.github.com>
|
||||||
|
- Alexander <alexanderchenmh@gmail.com>
|
||||||
**@alexanderdidenko (Aleksandr Didenko)**
|
- Alexander Lokhman <alex.lokhman@gmail.com>
|
||||||
- Add support multipart/form-data
|
- Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com>
|
||||||
|
- Alexander Nyquist <nyquist.alexander@gmail.com>
|
||||||
|
- Allen Ren <kulong0105@gmail.com>
|
||||||
**@alexandernyquist (Alexander Nyquist)**
|
- AllinGo <tanhp@outlook.com>
|
||||||
- Using template.Must to fix multiple return issue
|
- Ammar Bandukwala <ammar@ammar.io>
|
||||||
- ★ Added support for OPTIONS verb
|
- An Xiao (Luffy) <hac@zju.edu.cn>
|
||||||
- ★ Setting response headers before calling WriteHeader
|
- Andre Dublin <81dublin@gmail.com>
|
||||||
- Improved documentation for model binding
|
- Andrew Szeto <github@jabagawee.com>
|
||||||
- ★ Added Content.Redirect()
|
- Andrey Abramov <andreyabramov.aaa@gmail.com>
|
||||||
- ★ Added tons of Unit tests
|
- Andrey Nering <andrey.nering@gmail.com>
|
||||||
|
- Andrey Smirnov <Smirnov.Andrey@gmail.com>
|
||||||
|
- Andrii Bubis <firstrow@gmail.com>
|
||||||
**@austinheap (Austin Heap)**
|
- André Bazaglia <bazaglia@users.noreply.github.com>
|
||||||
- Added travis CI integration
|
- Andy Pan <panjf2000@gmail.com>
|
||||||
|
- Antoine GIRARD <sapk@users.noreply.github.com>
|
||||||
|
- Anup Kumar Panwar <1anuppanwar@gmail.com>
|
||||||
**@andredublin (Andre Dublin)**
|
- Aravinth Sundaram <gosh.aravind@gmail.com>
|
||||||
- Fix typo in comment
|
- Artem <horechek@gmail.com>
|
||||||
|
- Ashwani <ashwanisharma686@gmail.com>
|
||||||
|
- Aurelien Regat-Barrel <arb@cyberkarma.net>
|
||||||
**@bredov (Ludwig Valda Vasquez)**
|
- Austin Heap <me@austinheap.com>
|
||||||
- Fix html templating in debug mode
|
- Barnabus <jbampton@users.noreply.github.com>
|
||||||
|
- Bo-Yi Wu <appleboy.tw@gmail.com>
|
||||||
|
- Boris Borshevsky <BorisBorshevsky@gmail.com>
|
||||||
**@bluele (Jun Kimura)**
|
- Boyi Wu <p581581@gmail.com>
|
||||||
- Fixes code examples in README
|
- BradyBromley <51128276+BradyBromley@users.noreply.github.com>
|
||||||
|
- Brendan Fosberry <brendan@shopkeep.com>
|
||||||
|
- Brian Wigginton <brianwigginton@gmail.com>
|
||||||
**@chad-russell**
|
- Carlos Eduardo <carlosedp@gmail.com>
|
||||||
- ★ Support for serializing gin.H into XML
|
- Chad Russell <chaddouglasrussell@gmail.com>
|
||||||
|
- Charles <cxjava@gmail.com>
|
||||||
|
- Christian Muehlhaeuser <muesli@gmail.com>
|
||||||
**@dickeyxxx (Jeff Dickey)**
|
- Christian Persson <saser@live.se>
|
||||||
- Typos in README
|
- Christopher Harrington <ironiridis@gmail.com>
|
||||||
- Add example about serving static files
|
- Damon Zhao <yijun.zhao@outlook.com>
|
||||||
|
- Dan Markham <dmarkham@gmail.com>
|
||||||
|
- Dang Nguyen <hoangdang.me@gmail.com>
|
||||||
**@donileo (Adonis)**
|
- Daniel Krom <kromdan@gmail.com>
|
||||||
- Add NoMethod handler
|
- Daniel M. Lambea <dmlambea@gmail.com>
|
||||||
|
- Danieliu <liudanking@gmail.com>
|
||||||
|
- David Irvine <aviddiviner@gmail.com>
|
||||||
**@dutchcoders (DutchCoders)**
|
- David Zhang <crispgm@gmail.com>
|
||||||
- ★ Fix security bug that allows client to spoof ip
|
- Davor Kapsa <dvrkps@users.noreply.github.com>
|
||||||
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
|
- DeathKing <DeathKing@users.noreply.github.com>
|
||||||
|
- Dennis Cho <47404603+forest747@users.noreply.github.com>
|
||||||
|
- Dmitry Dorogin <dmirogin@ya.ru>
|
||||||
**@el3ctro- (Joshua Loper)**
|
- Dmitry Kutakov <vkd.castle@gmail.com>
|
||||||
- Fix typo in example
|
- Dmitry Sedykh <dmitrys@d3h.local>
|
||||||
|
- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com>
|
||||||
|
- Donn Pebe <iam@donnpebe.com>
|
||||||
**@ethankan (Ethan Kan)**
|
- Dustin Decker <dustindecker@protonmail.com>
|
||||||
- Unsigned integers in binding
|
- Eason Lin <easonlin404@gmail.com>
|
||||||
|
- Edward Betts <edward@4angle.com>
|
||||||
|
- Egor Seredin <4819888+agmt@users.noreply.github.com>
|
||||||
**(Evgeny Persienko)**
|
- Emmanuel Goh <emmanuel@visenze.com>
|
||||||
- Validate sub structures
|
- Equim <sayaka@ekyu.moe>
|
||||||
|
- Eren A. Akyol <eren@redmc.me>
|
||||||
|
- Eric_Lee <xplzv@126.com>
|
||||||
**@frankbille (Frank Bille)**
|
- Erik Bender <erik.bender@develerik.dev>
|
||||||
- Add support for HTTP Realm Auth
|
- Ethan Kan <ethankan@neoplot.com>
|
||||||
|
- Evgeny Persienko <e.persienko@office.ngs.ru>
|
||||||
|
- Faisal Alam <ifaisalalam@gmail.com>
|
||||||
**@fmd (Fareed Dudhia)**
|
- Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||||
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
- Filip Figiel <figiel.filip@gmail.com>
|
||||||
|
- Florian Polster <couchpolster@icqmail.com>
|
||||||
|
- Frank Bille <github@frankbille.dk>
|
||||||
**@ironiridis (Christopher Harrington)**
|
- Franz Bettag <franz@bett.ag>
|
||||||
- Remove old reference
|
- Ganlv <ganlvtech@users.noreply.github.com>
|
||||||
|
- Gaozhen Ying <yinggaozhen@hotmail.com>
|
||||||
|
- George Gabolaev <gabolaev98@gmail.com>
|
||||||
**@jammie-stackhouse (Jamie Stackhouse)**
|
- George Kirilenko <necryin@users.noreply.github.com>
|
||||||
- Add more shortcuts for router methods
|
- Georges Varouchas <georges.varouchas@gmail.com>
|
||||||
|
- Gordon Tyler <gordon@doxxx.net>
|
||||||
|
- Harindu Perera <harinduenator@gmail.com>
|
||||||
**@jasonrhansen**
|
- Helios <674876158@qq.com>
|
||||||
- Fix spelling and grammar errors in documentation
|
- Henry Kwan <piengeng@users.noreply.github.com>
|
||||||
|
- Henry Yee <henry@yearning.io>
|
||||||
|
- Himanshu Mishra <OrkoHunter@users.noreply.github.com>
|
||||||
**@JasonSoft (Jason Lee)**
|
- Hiroyuki Tanaka <h.tanaka.0325@gmail.com>
|
||||||
- Fix typo in comment
|
- Ibraheem Ahmed <ibrah1440@gmail.com>
|
||||||
|
- Ignacio Galindo <joiggama@gmail.com>
|
||||||
|
- Igor H. Vieira <zignd.igor@gmail.com>
|
||||||
**@joiggama (Ignacio Galindo)**
|
- Ildar1111 <54001462+Ildar1111@users.noreply.github.com>
|
||||||
- Add utf-8 charset header on renders
|
- Iskander (Alex) Sharipov <iskander.sharipov@intel.com>
|
||||||
|
- Ismail Gjevori <isgjevori@protonmail.com>
|
||||||
|
- Ivan Chen <allenivan@gmail.com>
|
||||||
**@julienschmidt (Julien Schmidt)**
|
- JINNOUCHI Yasushi <delphinus@remora.cx>
|
||||||
- gofmt the code examples
|
- James Pettyjohn <japettyjohn@users.noreply.github.com>
|
||||||
|
- Jamie Stackhouse <jamie.stackhouse@redspace.com>
|
||||||
|
- Jason Lee <jawc@hotmail.com>
|
||||||
**@kelcecil (Kel Cecil)**
|
- Javier Provecho <j.provecho@dartekstudios.com>
|
||||||
- Fix readme typo
|
- Javier Provecho <javier.provecho@bq.com>
|
||||||
|
- Javier Provecho <javiertitan@gmail.com>
|
||||||
|
- Javier Provecho Fernandez <j.provecho@dartekstudios.com>
|
||||||
**@kyledinh (Kyle Dinh)**
|
- Javier Provecho Fernandez <javiertitan@gmail.com>
|
||||||
- Adds RunTLS()
|
- Jean-Christophe Lebreton <jclebreton@gmail.com>
|
||||||
|
- Jeff <laojianzi1994@gmail.com>
|
||||||
|
- Jeremy Loy <jeremy.b.loy@icloud.com>
|
||||||
**@LinusU (Linus Unnebäck)**
|
- Jim Filippou <p3160253@aueb.gr>
|
||||||
- Small fixes in README
|
- Jimmy Pettersson <jimmy@expertmaker.com>
|
||||||
|
- John Bampton <jbampton@users.noreply.github.com>
|
||||||
|
- Johnny Dallas <johnnydallas0308@gmail.com>
|
||||||
**@loongmxbt (Saint Asky)**
|
- Johnny Dallas <theonlyjohnny@theonlyjohnny.sh>
|
||||||
- Fix typo in example
|
- Jonathan (JC) Chen <jc@dijonkitchen.org>
|
||||||
|
- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com>
|
||||||
|
- Josh Horowitz <joshua.m.horowitz@gmail.com>
|
||||||
**@lucas-clemente (Lucas Clemente)**
|
- Joshua Loper <josh.el3@gmail.com>
|
||||||
- ★ work around path.Join removing trailing slashes from routes
|
- Julien Schmidt <github@julienschmidt.com>
|
||||||
|
- Jun Kimura <jksmphone@gmail.com>
|
||||||
|
- Justin Beckwith <justin.beckwith@gmail.com>
|
||||||
**@mattn (Yasuhiro Matsumoto)**
|
- Justin Israel <justinisrael@gmail.com>
|
||||||
- Improve color logger
|
- Justin Mayhew <mayhew@live.ca>
|
||||||
|
- Jérôme Laforge <jerome-laforge@users.noreply.github.com>
|
||||||
|
- Kacper Bąk <56700396+53jk1@users.noreply.github.com>
|
||||||
**@mdigger (Dmitry Sedykh)**
|
- Kamron Batman <kamronbatman@users.noreply.github.com>
|
||||||
- Fixes Form binding when content-type is x-www-form-urlencoded
|
- Kane Rogers <kane@cleanstream.com.au>
|
||||||
- No repeat call c.Writer.Status() in gin.Logger
|
- Kaushik Neelichetty <kaushikneelichetty6132@gmail.com>
|
||||||
- Fixes Content-Type for json render
|
- Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||||
|
- Kel Cecil <kel.cecil@listhub.com>
|
||||||
|
- Kevin Mulvey <kmulvey@linux.com>
|
||||||
**@mirzac (Mirza Ceric)**
|
- Kevin Zhu <ipandtcp@gmail.com>
|
||||||
- Fix debug printing
|
- Kirill Motkov <motkov.kirill@gmail.com>
|
||||||
|
- Klemen Sever <ksever@student.42.fr>
|
||||||
|
- Kristoffer A. Iversen <kristoffer.a.iversen@gmail.com>
|
||||||
**@mopemope (Yutaka Matsubara)**
|
- Krzysztof Szafrański <k.p.szafranski@gmail.com>
|
||||||
- ★ Adds Godep support (Dependencies Manager)
|
- Kumar McMillan <kumar.mcmillan@gmail.com>
|
||||||
- Fix variadic parameter in the flexible render API
|
- Kyle Mcgill <email@kylescottmcgill.com>
|
||||||
- Fix Corrupted plain render
|
- Lanco <35420416+lancoLiu@users.noreply.github.com>
|
||||||
- Add Pluggable View Renderer Example
|
- Levi Olson <olson.levi@gmail.com>
|
||||||
|
- Lin Kao-Yuan <mosdeo@gmail.com>
|
||||||
|
- Linus Unnebäck <linus@folkdatorn.se>
|
||||||
**@msemenistyi (Mykyta Semenistyi)**
|
- Lucas Clemente <lucas@clemente.io>
|
||||||
- update Readme.md. Add code to String method
|
- Ludwig Valda Vasquez <bredov@gmail.com>
|
||||||
|
- Luis GG <lggomez@users.noreply.github.com>
|
||||||
|
- MW Lim <williamchange@gmail.com>
|
||||||
**@msoedov (Sasha Myasoedov)**
|
- Maksimov Sergey <konjoot@gmail.com>
|
||||||
- ★ Adds tons of unit tests.
|
- Manjusaka <lizheao940510@gmail.com>
|
||||||
|
- Manu MA <manu.mtza@gmail.com>
|
||||||
|
- Manu MA <manu.valladolid@gmail.com>
|
||||||
**@ngerakines (Nick Gerakines)**
|
- Manu Mtz-Almeida <manu.valladolid@gmail.com>
|
||||||
- ★ Improves API, c.GET() doesn't panic
|
- Manu Mtz.-Almeida <manu.valladolid@gmail.com>
|
||||||
- Adds MustGet() method
|
- Manuel Alonso <manuelalonso@invisionapp.com>
|
||||||
|
- Mara Kim <hacker.root@gmail.com>
|
||||||
|
- Mario Kostelac <mario@intercom.io>
|
||||||
**@r8k (Rajiv Kilaparti)**
|
- Martin Karlsch <martin@karlsch.com>
|
||||||
- Fix Port usage in README.
|
- Matt Newberry <mnewberry@opentable.com>
|
||||||
|
- Matt Williams <gh@mattyw.net>
|
||||||
|
- Matthieu MOREL <mmorel-35@users.noreply.github.com>
|
||||||
**@rayrod2030 (Ray Rodriguez)**
|
- Max Hilbrunner <mhilbrunner@users.noreply.github.com>
|
||||||
- Fix typo in example
|
- Maxime Soulé <btik-git@scoubidou.com>
|
||||||
|
- MetalBreaker <johnymichelson@gmail.com>
|
||||||
|
- Michael Puncel <mpuncel@squareup.com>
|
||||||
**@rns**
|
- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com>
|
||||||
- Fix typo in example
|
- Mike <38686456+icy4ever@users.noreply.github.com>
|
||||||
|
- Mike Stipicevic <mst@ableton.com>
|
||||||
|
- Miki Tebeka <miki.tebeka@gmail.com>
|
||||||
**@RobAWilkinson (Robert Wilkinson)**
|
- Miles <MilesLin@users.noreply.github.com>
|
||||||
- Add example of forms and params
|
- Mirza Ceric <mirza.ceric@b2match.com>
|
||||||
|
- Mykyta Semenistyi <nikeiwe@gmail.com>
|
||||||
|
- Naoki Takano <honten@tinkermode.com>
|
||||||
**@rogierlommers (Rogier Lommers)**
|
- Ngalim Siregar <ngalim.siregar@gmail.com>
|
||||||
- Add updated static serve example
|
- Ni Hao <supernihaooo@qq.com>
|
||||||
|
- Nick Gerakines <nick@gerakines.net>
|
||||||
|
- Nikifor Seryakov <nikandfor@gmail.com>
|
||||||
**@se77en (Damon Zhao)**
|
- Notealot <714804968@qq.com>
|
||||||
- Improve color logging
|
- Olivier Mengué <dolmen@cpan.org>
|
||||||
|
- Olivier Robardet <orobardet@users.noreply.github.com>
|
||||||
|
- Pablo Moncada <pablo.moncada@bq.com>
|
||||||
**@silasb (Silas Baronda)**
|
- Pablo Moncada <pmoncadaisla@gmail.com>
|
||||||
- Fixing quotes in README
|
- Panmax <967168@qq.com>
|
||||||
|
- Peperoncino <2wua4nlyi@gmail.com>
|
||||||
|
- Philipp Meinen <philipp@bind.ch>
|
||||||
**@SkuliOskarsson (Skuli Oskarsson)**
|
- Pierre Massat <pierre@massat.io>
|
||||||
- Fixes some texts in README II
|
- Qt <golang.chen@gmail.com>
|
||||||
|
- Quentin ROYER <aydendevg@gmail.com>
|
||||||
|
- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com>
|
||||||
**@slimmy (Jimmy Pettersson)**
|
- Rafal Zajac <rzajac@gmail.com>
|
||||||
- Added messages for required bindings
|
- Rahul Datta Roy <rahuldroy@users.noreply.github.com>
|
||||||
|
- Rajiv Kilaparti <rajivk085@gmail.com>
|
||||||
|
- Raphael Gavache <raphael.gavache@datadoghq.com>
|
||||||
**@smira (Andrey Smirnov)**
|
- Ray Rodriguez <rayrod2030@gmail.com>
|
||||||
- Add support for ignored/unexported fields in binding
|
- Regner Blok-Andersen <shadowdf@gmail.com>
|
||||||
|
- Remco <remco@dutchcoders.io>
|
||||||
|
- Rex Lee(李俊) <duguying2008@gmail.com>
|
||||||
**@superalsrk (SRK.Lyu)**
|
- Richard Lee <dlackty@gmail.com>
|
||||||
- Update httprouter godeps
|
- Riverside <wangyb65@gmail.com>
|
||||||
|
- Robert Wilkinson <wilkinson.robert.a@gmail.com>
|
||||||
|
- Rogier Lommers <rogier@lommers.org>
|
||||||
**@tebeka (Miki Tebeka)**
|
- Rohan Pai <me@rohanpai.com>
|
||||||
- Use net/http constants instead of numeric values
|
- Romain Beuque <rbeuque74@gmail.com>
|
||||||
|
- Roman Belyakovsky <ihryamzik@gmail.com>
|
||||||
|
- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com>
|
||||||
**@techjanitor**
|
- Roman Zaynetdinov <roman.zaynetdinov@lekane.com>
|
||||||
- Update context.go reserved IPs
|
- Ronald Petty <ronald.petty@rx-m.com>
|
||||||
|
- Ross Wolf <31489089+rw-access@users.noreply.github.com>
|
||||||
|
- Roy Lou <roylou@gmail.com>
|
||||||
**@yosssi (Keiji Yoshida)**
|
- Rubi <14269809+codenoid@users.noreply.github.com>
|
||||||
- Fix link in README
|
- Ryan <46182144+ryanker@users.noreply.github.com>
|
||||||
|
- Ryan J. Yoder <me@ryanjyoder.com>
|
||||||
|
- SRK.Lyu <superalsrk@gmail.com>
|
||||||
**@yuyabee**
|
- Sai <sairoutine@gmail.com>
|
||||||
- Fixed README
|
- Samuel Abreu <sdepaula@gmail.com>
|
||||||
|
- Santhosh Kumar <santhoshkumarr1096@gmail.com>
|
||||||
|
- Sasha Melentyev <sasha@melentyev.io>
|
||||||
|
- Sasha Myasoedov <msoedov@gmail.com>
|
||||||
|
- Segev Finer <segev208@gmail.com>
|
||||||
|
- Sergey Egorov <egorovhome@gmail.com>
|
||||||
|
- Sergey Fedchenko <seregayoga@bk.ru>
|
||||||
|
- Sergey Gonimar <sergey.gonimar@gmail.com>
|
||||||
|
- Sergey Ponomarev <me@sergey-ponomarev.ru>
|
||||||
|
- Serica <943914044@qq.com>
|
||||||
|
- Shamus Taylor <Shamus03@me.com>
|
||||||
|
- Shilin Wang <jarvisfironman@gmail.com>
|
||||||
|
- Shuo <openset.wang@gmail.com>
|
||||||
|
- Skuli Oskarsson <skuli@codeiak.io>
|
||||||
|
- Snawoot <vladislav-ex-github@vm-0.com>
|
||||||
|
- Sridhar Ratnakumar <srid@srid.ca>
|
||||||
|
- Steeve Chailloux <steeve@chaahk.com>
|
||||||
|
- Sudhir Mishra <sudhirxps@gmail.com>
|
||||||
|
- Suhas Karanth <sudo-suhas@users.noreply.github.com>
|
||||||
|
- TaeJun Park <miking38@gmail.com>
|
||||||
|
- Tatsuya Hoshino <tatsuya7.hoshino7@gmail.com>
|
||||||
|
- Tevic <tevic.tt@gmail.com>
|
||||||
|
- Tevin Jeffrey <tev.jeffrey@gmail.com>
|
||||||
|
- The Gitter Badger <badger@gitter.im>
|
||||||
|
- Thibault Jamet <tjamet@users.noreply.github.com>
|
||||||
|
- Thomas Boerger <thomas@webhippie.de>
|
||||||
|
- Thomas Schaffer <loopfz@gmail.com>
|
||||||
|
- Tommy Chu <tommychu2256@gmail.com>
|
||||||
|
- Tudor Roman <tudurom@gmail.com>
|
||||||
|
- Uwe Dauernheim <djui@users.noreply.github.com>
|
||||||
|
- Valentine Oragbakosi <valentine13400@gmail.com>
|
||||||
|
- Vas N <pnvasanth@users.noreply.github.com>
|
||||||
|
- Vasilyuk Vasiliy <By-Vasiliy@users.noreply.github.com>
|
||||||
|
- Victor Castell <victor@victorcastell.com>
|
||||||
|
- Vince Yuan <vince.yuan@gmail.com>
|
||||||
|
- Vyacheslav Dubinin <vyacheslav.dubinin@gmail.com>
|
||||||
|
- Waynerv <ampedee@gmail.com>
|
||||||
|
- Weilin Shi <934587911@qq.com>
|
||||||
|
- Xudong Cai <fifsky@gmail.com>
|
||||||
|
- Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
||||||
|
- Yehezkiel Syamsuhadi <ybs@ybs.im>
|
||||||
|
- Yoshiki Nakagawa <yyoshiki41@gmail.com>
|
||||||
|
- Yoshiyuki Kinjo <yskkin+github@gmail.com>
|
||||||
|
- Yue Yang <g1enyy0ung@gmail.com>
|
||||||
|
- ZYunH <zyunhjob@163.com>
|
||||||
|
- Zach Newburgh <zach.newburgh@gmail.com>
|
||||||
|
- Zasda Yusuf Mikail <zasdaym@gmail.com>
|
||||||
|
- ZhangYunHao <zyunhjob@163.com>
|
||||||
|
- ZhiFeng Hu <hufeng1987@gmail.com>
|
||||||
|
- Zhu Xi <zhuxi910511@163.com>
|
||||||
|
- a2tt <usera2tt@gmail.com>
|
||||||
|
- ahuigo <1781999+ahuigo@users.noreply.github.com>
|
||||||
|
- ali <anio@users.noreply.github.com>
|
||||||
|
- aljun <salameryy@163.com>
|
||||||
|
- andrea <crypto.andrea@protonmail.ch>
|
||||||
|
- andriikushch <andrii.kushch@gmail.com>
|
||||||
|
- anoty <anjunyou@foxmail.com>
|
||||||
|
- awkj <hzzbiu@gmail.com>
|
||||||
|
- axiaoxin <254606826@qq.com>
|
||||||
|
- bbiao <bbbiao@gmail.com>
|
||||||
|
- bestgopher <84328409@qq.com>
|
||||||
|
- betahu <zhong.wenhuang@foxmail.com>
|
||||||
|
- bigwheel <k.bigwheel+eng@gmail.com>
|
||||||
|
- bn4t <17193640+bn4t@users.noreply.github.com>
|
||||||
|
- bullgare <bullgare@gmail.com>
|
||||||
|
- chainhelen <chainhelen@gmail.com>
|
||||||
|
- chenyang929 <chenyang929code@gmail.com>
|
||||||
|
- chriswhelix <chris.williams@helix.com>
|
||||||
|
- collinmsn <4130944@qq.com>
|
||||||
|
- cssivision <cssivision@gmail.com>
|
||||||
|
- danielalves <alves.lopes.dan@gmail.com>
|
||||||
|
- delphinus <delphinus@remora.cx>
|
||||||
|
- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
|
- dickeyxxx <jeff@dickeyxxx.com>
|
||||||
|
- edebernis <emeric.debernis@gmail.com>
|
||||||
|
- error10 <error@ioerror.us>
|
||||||
|
- esplo <esplo@users.noreply.github.com>
|
||||||
|
- eudore <30709860+eudore@users.noreply.github.com>
|
||||||
|
- ffhelicopter <32922889+ffhelicopter@users.noreply.github.com>
|
||||||
|
- filikos <11477309+filikos@users.noreply.github.com>
|
||||||
|
- forging2012 <forging2012@users.noreply.github.com>
|
||||||
|
- goqihoo <goqihoo@gmail.com>
|
||||||
|
- grapeVine <treeui.old@gmail.com>
|
||||||
|
- guonaihong <guonaihong@qq.com>
|
||||||
|
- heige <daheige@users.noreply.github.com>
|
||||||
|
- heige <zhuwei313@hotmail.com>
|
||||||
|
- hellojukay <hellojukay@163.com>
|
||||||
|
- henrylee2cn <henrylee2cn@gmail.com>
|
||||||
|
- htobenothing <htobenothing@gmail.com>
|
||||||
|
- iamhesir <78344375+iamhesir@users.noreply.github.com>
|
||||||
|
- ijaa <kailiu2013@gmail.com>
|
||||||
|
- ishanray <ishan.iipm@gmail.com>
|
||||||
|
- ishanray <ishanray@users.noreply.github.com>
|
||||||
|
- itcloudy <272685110@qq.com>
|
||||||
|
- jarodsong6 <jarodsong6@gmail.com>
|
||||||
|
- jasonrhansen <jasonrodneyhansen@gmail.com>
|
||||||
|
- jincheng9 <perfume0607@gmail.com>
|
||||||
|
- joeADSP <75027008+joeADSP@users.noreply.github.com>
|
||||||
|
- junfengye <junfeng.yejf@gmail.com>
|
||||||
|
- kaiiak <aNxFi37X@outlook.com>
|
||||||
|
- kebo <kevinke2020@outlook.com>
|
||||||
|
- keke <19yamashita15@gmail.com>
|
||||||
|
- kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com>
|
||||||
|
- kyledinh <kyledinh@gmail.com>
|
||||||
|
- lantw44 <lantw44@gmail.com>
|
||||||
|
- likakuli <1154584512@qq.com>
|
||||||
|
- linfangrong <linfangrong.liuxin@qq.com>
|
||||||
|
- linzi <873804682@qq.com>
|
||||||
|
- llgoer <yanghuxiao@vip.qq.com>
|
||||||
|
- long-road <13412081338@163.com>
|
||||||
|
- mbesancon <mathieu.besancon@gmail.com>
|
||||||
|
- mehdy <mehdy.khoshnoody@gmail.com>
|
||||||
|
- metal A-wing <freedom.awing.777@gmail.com>
|
||||||
|
- micanzhang <micanzhang@gmail.com>
|
||||||
|
- minarc <ragnhildmowinckel@gmail.com>
|
||||||
|
- mllu <mornlyn@gmail.com>
|
||||||
|
- mopemoepe <yutaka.matsubara@gmail.com>
|
||||||
|
- msoedov <msoedov@gmail.com>
|
||||||
|
- mstmdev <mstmdev@gmail.com>
|
||||||
|
- novaeye <fcoffee@gmail.com>
|
||||||
|
- olebedev <oolebedev@gmail.com>
|
||||||
|
- phithon <phith0n@users.noreply.github.com>
|
||||||
|
- pjgg <pablo.gonzalez.granados@gmail.com>
|
||||||
|
- qm012 <67568757+qm012@users.noreply.github.com>
|
||||||
|
- raymonder jin <rayjingithub@gmail.com>
|
||||||
|
- rns <ruslan.shvedov@gmail.com>
|
||||||
|
- root@andrea:~# <crypto.andrea@protonmail.ch>
|
||||||
|
- sekky0905 <20237968+sekky0905@users.noreply.github.com>
|
||||||
|
- senhtry <w169q169@gmail.com>
|
||||||
|
- shadrus <shadrus@gmail.com>
|
||||||
|
- silasb <silas.baronda@gmail.com>
|
||||||
|
- solos <lxl1217@gmail.com>
|
||||||
|
- songjiayang <songjiayang@users.noreply.github.com>
|
||||||
|
- sope <shenshouer@163.com>
|
||||||
|
- srt180 <30768686+srt180@users.noreply.github.com>
|
||||||
|
- stackerzzq <foo_stacker@yeah.net>
|
||||||
|
- sunshineplan <sunshineplan@users.noreply.github.com>
|
||||||
|
- syssam <s.y.s.sam.sys@gmail.com>
|
||||||
|
- techjanitor <puntme@gmail.com>
|
||||||
|
- techjanitor <techjanitor@users.noreply.github.com>
|
||||||
|
- thinkerou <thinkerou@gmail.com>
|
||||||
|
- thinkgo <49174849+thinkgos@users.noreply.github.com>
|
||||||
|
- tsirolnik <tsirolnik@users.noreply.github.com>
|
||||||
|
- tyltr <31768692+tylitianrui@users.noreply.github.com>
|
||||||
|
- vinhha96 <anhvinha1@gmail.com>
|
||||||
|
- voidman <retmain@foxmail.com>
|
||||||
|
- vz <vzvway@gmail.com>
|
||||||
|
- wei <wei840222@gmail.com>
|
||||||
|
- weibaohui <weibaohui@yeah.net>
|
||||||
|
- whirosan <whirosan@users.noreply.github.com>
|
||||||
|
- willnewrelic <will@newrelic.com>
|
||||||
|
- wssccc <wssccc@qq.com>
|
||||||
|
- wuhuizuo <wuhuizuo@126.com>
|
||||||
|
- xyb <xyb4638@gmail.com>
|
||||||
|
- y-yagi <yuuji.yaginuma@gmail.com>
|
||||||
|
- yiranzai <wuqingdzx@gmail.com>
|
||||||
|
- youzeliang <youzel@126.com>
|
||||||
|
- yugu <chenzilong_1227@foxmail.com>
|
||||||
|
- yuyabe <yuyabee@gmail.com>
|
||||||
|
- zebozhuang <zebozhuang@163.com>
|
||||||
|
- zero11-0203 <93071220+zero11-0203@users.noreply.github.com>
|
||||||
|
- zesani <7sin@outlook.co.th>
|
||||||
|
- zhanweidu <zhanweidu@163.com>
|
||||||
|
- zhing <zqwillseven@gmail.com>
|
||||||
|
- ziheng <zihenglv@gmail.com>
|
||||||
|
- zzjin <zzjin@users.noreply.github.com>
|
||||||
|
- 森 優太 <59682979+uta-mori@users.noreply.github.com>
|
||||||
|
- 杰哥 <858806258@qq.com>
|
||||||
|
- 涛叔 <hi@taoshu.in>
|
||||||
|
- 市民233 <mengrenxiong@gmail.com>
|
||||||
|
- 尹宝强 <wmdandme@gmail.com>
|
||||||
|
- 梦溪笔谈 <loongmxbt@gmail.com>
|
||||||
|
- 飞雪无情 <ls8707@gmail.com>
|
||||||
|
- 寻寻觅觅的Gopher <zoujh99@qq.com>
|
||||||
|
|||||||
129
CHANGELOG.md
129
CHANGELOG.md
@ -1,5 +1,134 @@
|
|||||||
# Gin ChangeLog
|
# Gin ChangeLog
|
||||||
|
|
||||||
|
## Gin v1.8.1
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* feat(context): add ContextWithFallback feature flag [#3172](https://github.com/gin-gonic/gin/pull/3172)
|
||||||
|
|
||||||
|
## Gin v1.8.0
|
||||||
|
|
||||||
|
## Break Changes
|
||||||
|
|
||||||
|
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP`
|
||||||
|
* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751)
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861)
|
||||||
|
* Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)
|
||||||
|
* Fix: missing sameSite when do context.reset() [#3123](https://github.com/gin-gonic/gin/pull/3123)
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* Use Header() instead of deprecated HeaderMap [#2694](https://github.com/gin-gonic/gin/pull/2694)
|
||||||
|
* RouterGroup.Handle regular match optimization of http method [#2685](https://github.com/gin-gonic/gin/pull/2685)
|
||||||
|
* Add support go-json, another drop-in json replacement [#2680](https://github.com/gin-gonic/gin/pull/2680)
|
||||||
|
* Use errors.New to replace fmt.Errorf will much better [#2707](https://github.com/gin-gonic/gin/pull/2707)
|
||||||
|
* Use Duration.Truncate for truncating precision [#2711](https://github.com/gin-gonic/gin/pull/2711)
|
||||||
|
* Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723)
|
||||||
|
* Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files)
|
||||||
|
* Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737)
|
||||||
|
* Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765)
|
||||||
|
* Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720)
|
||||||
|
* Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787)
|
||||||
|
* Fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() [#2769](https://github.com/gin-gonic/gin/pull/2769)
|
||||||
|
* Some codes optimize [#2830](https://github.com/gin-gonic/gin/pull/2830) [#2834](https://github.com/gin-gonic/gin/pull/2834) [#2838](https://github.com/gin-gonic/gin/pull/2838) [#2837](https://github.com/gin-gonic/gin/pull/2837) [#2788](https://github.com/gin-gonic/gin/pull/2788) [#2848](https://github.com/gin-gonic/gin/pull/2848) [#2851](https://github.com/gin-gonic/gin/pull/2851) [#2701](https://github.com/gin-gonic/gin/pull/2701)
|
||||||
|
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967)
|
||||||
|
* Test(route): expose performRequest func [#3012](https://github.com/gin-gonic/gin/pull/3012)
|
||||||
|
* Support h2c with prior knowledge [#1398](https://github.com/gin-gonic/gin/pull/1398)
|
||||||
|
* Feat attachment filename support utf8 [#3071](https://github.com/gin-gonic/gin/pull/3071)
|
||||||
|
* Feat: add StaticFileFS [#2749](https://github.com/gin-gonic/gin/pull/2749)
|
||||||
|
* Feat(context): return GIN Context from Value method [#2825](https://github.com/gin-gonic/gin/pull/2825)
|
||||||
|
* Feat: automatically SetMode to TestMode when run go test [#3139](https://github.com/gin-gonic/gin/pull/3139)
|
||||||
|
* Add TOML bining for gin [#3081](https://github.com/gin-gonic/gin/pull/3081)
|
||||||
|
* IPv6 add default trusted proxies [#3033](https://github.com/gin-gonic/gin/pull/3033)
|
||||||
|
|
||||||
|
### DOCS
|
||||||
|
|
||||||
|
* Add note about nomsgpack tag to the readme [#2703](https://github.com/gin-gonic/gin/pull/2703)
|
||||||
|
|
||||||
|
## Gin v1.7.7
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).
|
||||||
|
* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).
|
||||||
|
* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).
|
||||||
|
* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).
|
||||||
|
* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).
|
||||||
|
|
||||||
|
### DOCS
|
||||||
|
|
||||||
|
* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).
|
||||||
|
|
||||||
|
## Gin v1.7.6
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
|
||||||
|
|
||||||
|
## Gin v1.7.4
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* bump new release to fix checksum mismatch
|
||||||
|
|
||||||
|
## Gin v1.7.3
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)
|
||||||
|
|
||||||
|
## Gin v1.7.2
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).
|
||||||
|
|
||||||
|
## Gin v1.7.1
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675))
|
||||||
|
|
||||||
|
## Gin v1.7.0
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))
|
||||||
|
* fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))
|
||||||
|
* fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366))
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||||
|
* chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365))
|
||||||
|
* Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368))
|
||||||
|
* chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375))
|
||||||
|
* chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
|
||||||
|
* Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
|
||||||
|
* update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
|
||||||
|
* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
|
||||||
|
* Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
|
||||||
|
* Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
|
||||||
|
* binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
|
||||||
|
* Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412))
|
||||||
|
* Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487))
|
||||||
|
* update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512))
|
||||||
|
* reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508))
|
||||||
|
* implement ".Unwrap() error" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526))
|
||||||
|
* Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484))
|
||||||
|
* chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371))
|
||||||
|
* Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302))
|
||||||
|
* basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609))
|
||||||
|
* Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||||
|
* feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632))
|
||||||
|
|
||||||
## Gin v1.6.3
|
## Gin v1.6.3
|
||||||
|
|
||||||
### ENHANCEMENTS
|
### ENHANCEMENTS
|
||||||
|
|||||||
@ -8,6 +8,6 @@
|
|||||||
- With pull requests:
|
- With pull requests:
|
||||||
- Open your pull request against `master`
|
- Open your pull request against `master`
|
||||||
- Your pull request should have no more than two commits, if not you should squash them.
|
- Your pull request should have no more than two commits, if not you should squash them.
|
||||||
- It should pass all tests in the available continuous integration systems such as TravisCI.
|
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
|
||||||
- You should add/modify tests to cover your proposed code changes.
|
- You should add/modify tests to cover your proposed code changes.
|
||||||
- If your pull request contains a new feature, please document it on the README.
|
- If your pull request contains a new feature, please document it on the README.
|
||||||
|
|||||||
10
Makefile
10
Makefile
@ -1,5 +1,6 @@
|
|||||||
GO ?= go
|
GO ?= go
|
||||||
GOFMT ?= gofmt "-s"
|
GOFMT ?= gofmt "-s"
|
||||||
|
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||||
PACKAGES ?= $(shell $(GO) list ./...)
|
PACKAGES ?= $(shell $(GO) list ./...)
|
||||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||||
GOFILES := $(shell find . -name "*.go")
|
GOFILES := $(shell find . -name "*.go")
|
||||||
@ -67,5 +68,10 @@ misspell:
|
|||||||
|
|
||||||
.PHONY: tools
|
.PHONY: tools
|
||||||
tools:
|
tools:
|
||||||
go install golang.org/x/lint/golint; \
|
@if [ $(GO_VERSION) -gt 15 ]; then \
|
||||||
go install github.com/client9/misspell/cmd/misspell;
|
$(GO) install golang.org/x/lint/golint@latest; \
|
||||||
|
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
|
||||||
|
elif [ $(GO_VERSION) -lt 16 ]; then \
|
||||||
|
$(GO) install golang.org/x/lint/golint; \
|
||||||
|
$(GO) install github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
|||||||
260
README.md
260
README.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
||||||
|
|
||||||
[](https://travis-ci.org/gin-gonic/gin)
|
[](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster)
|
||||||
[](https://codecov.io/gh/gin-gonic/gin)
|
[](https://codecov.io/gh/gin-gonic/gin)
|
||||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||||
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
||||||
@ -23,7 +23,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [Quick start](#quick-start)
|
- [Quick start](#quick-start)
|
||||||
- [Benchmarks](#benchmarks)
|
- [Benchmarks](#benchmarks)
|
||||||
- [Gin v1. stable](#gin-v1-stable)
|
- [Gin v1. stable](#gin-v1-stable)
|
||||||
- [Build with jsoniter](#build-with-jsoniter)
|
- [Build with jsoniter/go-json](#build-with-json-replacement)
|
||||||
|
- [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
|
||||||
- [API Examples](#api-examples)
|
- [API Examples](#api-examples)
|
||||||
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||||
- [Parameters in path](#parameters-in-path)
|
- [Parameters in path](#parameters-in-path)
|
||||||
@ -77,6 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [http2 server push](#http2-server-push)
|
- [http2 server push](#http2-server-push)
|
||||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||||
|
- [Don't trust all proxies](#dont-trust-all-proxies)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
- [Users](#users)
|
- [Users](#users)
|
||||||
|
|
||||||
@ -84,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
|
|
||||||
To install Gin package, you need to install Go and set your Go workspace first.
|
To install Gin package, you need to install Go and set your Go workspace first.
|
||||||
|
|
||||||
1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin.
|
1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/gin-gonic/gin
|
$ go get -u github.com/gin-gonic/gin
|
||||||
@ -112,12 +114,16 @@ $ cat example.go
|
|||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "pong",
|
"message": "pong",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -182,13 +188,28 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
|
|||||||
- [x] Battle tested.
|
- [x] Battle tested.
|
||||||
- [x] API frozen, new releases will not break your code.
|
- [x] API frozen, new releases will not break your code.
|
||||||
|
|
||||||
## Build with [jsoniter](https://github.com/json-iterator/go)
|
## Build with json replacement
|
||||||
|
|
||||||
Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
|
Gin uses `encoding/json` as default json package but you can change it by build from other tags.
|
||||||
|
|
||||||
|
[jsoniter](https://github.com/json-iterator/go)
|
||||||
```sh
|
```sh
|
||||||
$ go build -tags=jsoniter .
|
$ go build -tags=jsoniter .
|
||||||
```
|
```
|
||||||
|
[go-json](https://github.com/goccy/go-json)
|
||||||
|
```sh
|
||||||
|
$ go build -tags=go_json .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build without `MsgPack` rendering feature
|
||||||
|
|
||||||
|
Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go build -tags=nomsgpack .
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
|
||||||
|
|
||||||
## API Examples
|
## API Examples
|
||||||
|
|
||||||
@ -240,7 +261,15 @@ func main() {
|
|||||||
|
|
||||||
// For each matched request Context will hold the route definition
|
// For each matched request Context will hold the route definition
|
||||||
router.POST("/user/:name/*action", func(c *gin.Context) {
|
router.POST("/user/:name/*action", func(c *gin.Context) {
|
||||||
c.FullPath() == "/user/:name/*action" // true
|
b := c.FullPath() == "/user/:name/*action" // true
|
||||||
|
c.String(http.StatusOK, "%t", b)
|
||||||
|
})
|
||||||
|
|
||||||
|
// This handler will add a new router for /user/groups.
|
||||||
|
// Exact routes are resolved before param routes, regardless of the order they were defined.
|
||||||
|
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
|
||||||
|
router.GET("/user/groups", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "The available groups are [...]")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -275,7 +304,7 @@ func main() {
|
|||||||
message := c.PostForm("message")
|
message := c.PostForm("message")
|
||||||
nick := c.DefaultPostForm("nick", "anonymous")
|
nick := c.DefaultPostForm("nick", "anonymous")
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"status": "posted",
|
"status": "posted",
|
||||||
"message": message,
|
"message": message,
|
||||||
"nick": nick,
|
"nick": nick,
|
||||||
@ -359,7 +388,7 @@ func main() {
|
|||||||
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||||
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||||
router.POST("/upload", func(c *gin.Context) {
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
// single file
|
// Single file
|
||||||
file, _ := c.FormFile("file")
|
file, _ := c.FormFile("file")
|
||||||
log.Println(file.Filename)
|
log.Println(file.Filename)
|
||||||
|
|
||||||
@ -488,6 +517,7 @@ func main() {
|
|||||||
|
|
||||||
// nested group
|
// nested group
|
||||||
testing := authorized.Group("testing")
|
testing := authorized.Group("testing")
|
||||||
|
// visit 0.0.0.0:8080/testing/analytics
|
||||||
testing.GET("/analytics", analyticsEndpoint)
|
testing.GET("/analytics", analyticsEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +574,7 @@ func main() {
|
|||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -576,7 +606,7 @@ func main() {
|
|||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -604,7 +634,7 @@ func main() {
|
|||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -623,7 +653,7 @@ func main() {
|
|||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -632,7 +662,7 @@ func main() {
|
|||||||
|
|
||||||
### Model binding and validation
|
### Model binding and validation
|
||||||
|
|
||||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
|
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
|
||||||
|
|
||||||
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
|
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
|
||||||
|
|
||||||
@ -640,10 +670,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
|
|||||||
|
|
||||||
Also, Gin provides two sets of methods for binding:
|
Also, Gin provides two sets of methods for binding:
|
||||||
- **Type** - Must bind
|
- **Type** - Must bind
|
||||||
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`
|
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
|
||||||
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
||||||
- **Type** - Should bind
|
- **Type** - Should bind
|
||||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`
|
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
|
||||||
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
|
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
|
||||||
|
|
||||||
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
|
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
|
||||||
@ -679,7 +709,7 @@ func main() {
|
|||||||
// Example for binding XML (
|
// Example for binding XML (
|
||||||
// <?xml version="1.0" encoding="UTF-8"?>
|
// <?xml version="1.0" encoding="UTF-8"?>
|
||||||
// <root>
|
// <root>
|
||||||
// <user>user</user>
|
// <user>manu</user>
|
||||||
// <password>123</password>
|
// <password>123</password>
|
||||||
// </root>)
|
// </root>)
|
||||||
router.POST("/loginXML", func(c *gin.Context) {
|
router.POST("/loginXML", func(c *gin.Context) {
|
||||||
@ -822,6 +852,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -844,7 +875,7 @@ func startPage(c *gin.Context) {
|
|||||||
log.Println(person.Name)
|
log.Println(person.Name)
|
||||||
log.Println(person.Address)
|
log.Println(person.Address)
|
||||||
}
|
}
|
||||||
c.String(200, "Success")
|
c.String(http.StatusOK, "Success")
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -858,6 +889,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -881,7 +913,7 @@ func startPage(c *gin.Context) {
|
|||||||
var person Person
|
var person Person
|
||||||
// If `GET`, only `Form` binding engine (`query`) used.
|
// If `GET`, only `Form` binding engine (`query`) used.
|
||||||
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
||||||
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
|
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
|
||||||
if c.ShouldBind(&person) == nil {
|
if c.ShouldBind(&person) == nil {
|
||||||
log.Println(person.Name)
|
log.Println(person.Name)
|
||||||
log.Println(person.Address)
|
log.Println(person.Address)
|
||||||
@ -890,7 +922,7 @@ func startPage(c *gin.Context) {
|
|||||||
log.Println(person.UnixTime)
|
log.Println(person.UnixTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.String(200, "Success")
|
c.String(http.StatusOK, "Success")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -906,7 +938,11 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/846).
|
|||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
ID string `uri:"id" binding:"required,uuid"`
|
ID string `uri:"id" binding:"required,uuid"`
|
||||||
@ -918,10 +954,10 @@ func main() {
|
|||||||
route.GET("/:name/:id", func(c *gin.Context) {
|
route.GET("/:name/:id", func(c *gin.Context) {
|
||||||
var person Person
|
var person Person
|
||||||
if err := c.ShouldBindUri(&person); err != nil {
|
if err := c.ShouldBindUri(&person); err != nil {
|
||||||
c.JSON(400, gin.H{"msg": err})
|
c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
|
c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
|
||||||
})
|
})
|
||||||
route.Run(":8088")
|
route.Run(":8088")
|
||||||
}
|
}
|
||||||
@ -940,6 +976,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -954,11 +992,11 @@ func main() {
|
|||||||
h := testHeader{}
|
h := testHeader{}
|
||||||
|
|
||||||
if err := c.ShouldBindHeader(&h); err != nil {
|
if err := c.ShouldBindHeader(&h); err != nil {
|
||||||
c.JSON(200, err)
|
c.JSON(http.StatusOK, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%#v\n", h)
|
fmt.Printf("%#v\n", h)
|
||||||
c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
|
c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Run()
|
r.Run()
|
||||||
@ -988,7 +1026,7 @@ type myForm struct {
|
|||||||
func formHandler(c *gin.Context) {
|
func formHandler(c *gin.Context) {
|
||||||
var fakeForm myForm
|
var fakeForm myForm
|
||||||
c.ShouldBind(&fakeForm)
|
c.ShouldBind(&fakeForm)
|
||||||
c.JSON(200, gin.H{"color": fakeForm.Colors})
|
c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
|
||||||
}
|
}
|
||||||
|
|
||||||
...
|
...
|
||||||
@ -1193,14 +1231,14 @@ func main() {
|
|||||||
|
|
||||||
// Serves unicode entities
|
// Serves unicode entities
|
||||||
r.GET("/json", func(c *gin.Context) {
|
r.GET("/json", func(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"html": "<b>Hello, world!</b>",
|
"html": "<b>Hello, world!</b>",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Serves literal characters
|
// Serves literal characters
|
||||||
r.GET("/purejson", func(c *gin.Context) {
|
r.GET("/purejson", func(c *gin.Context) {
|
||||||
c.PureJSON(200, gin.H{
|
c.PureJSON(http.StatusOK, gin.H{
|
||||||
"html": "<b>Hello, world!</b>",
|
"html": "<b>Hello, world!</b>",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1218,6 +1256,7 @@ func main() {
|
|||||||
router.Static("/assets", "./assets")
|
router.Static("/assets", "./assets")
|
||||||
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||||
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||||
|
router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
|
||||||
|
|
||||||
// Listen and serve on 0.0.0.0:8080
|
// Listen and serve on 0.0.0.0:8080
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
@ -1384,7 +1423,7 @@ import (
|
|||||||
|
|
||||||
func formatAsDate(t time.Time) string {
|
func formatAsDate(t time.Time) string {
|
||||||
year, month, day := t.Date()
|
year, month, day := t.Date()
|
||||||
return fmt.Sprintf("%d%02d/%02d", year, month, day)
|
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -1446,7 +1485,7 @@ r.GET("/test", func(c *gin.Context) {
|
|||||||
r.HandleContext(c)
|
r.HandleContext(c)
|
||||||
})
|
})
|
||||||
r.GET("/test2", func(c *gin.Context) {
|
r.GET("/test2", func(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"hello": "world"})
|
c.JSON(http.StatusOK, gin.H{"hello": "world"})
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1608,6 +1647,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/autotls"
|
"github.com/gin-gonic/autotls"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -1618,7 +1658,7 @@ func main() {
|
|||||||
|
|
||||||
// Ping handler
|
// Ping handler
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
|
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
|
||||||
@ -1632,6 +1672,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/autotls"
|
"github.com/gin-gonic/autotls"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -1643,7 +1684,7 @@ func main() {
|
|||||||
|
|
||||||
// Ping handler
|
// Ping handler
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
m := autocert.Manager{
|
m := autocert.Manager{
|
||||||
@ -1802,8 +1843,8 @@ func main() {
|
|||||||
// Initializing the server in a goroutine so that
|
// Initializing the server in a goroutine so that
|
||||||
// it won't block the graceful shutdown handling below
|
// it won't block the graceful shutdown handling below
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("listen: %s\n", err)
|
log.Printf("listen: %s\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -1812,7 +1853,7 @@ func main() {
|
|||||||
quit := make(chan os.Signal)
|
quit := make(chan os.Signal)
|
||||||
// kill (no param) default send syscall.SIGTERM
|
// kill (no param) default send syscall.SIGTERM
|
||||||
// kill -2 is syscall.SIGINT
|
// kill -2 is syscall.SIGINT
|
||||||
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
log.Println("Shutting down server...")
|
log.Println("Shutting down server...")
|
||||||
@ -1821,6 +1862,7 @@ func main() {
|
|||||||
// the request it is currently handling
|
// the request it is currently handling
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server forced to shutdown:", err)
|
log.Fatal("Server forced to shutdown:", err)
|
||||||
}
|
}
|
||||||
@ -1903,7 +1945,7 @@ type StructD struct {
|
|||||||
func GetDataB(c *gin.Context) {
|
func GetDataB(c *gin.Context) {
|
||||||
var b StructB
|
var b StructB
|
||||||
c.Bind(&b)
|
c.Bind(&b)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"a": b.NestedStruct,
|
"a": b.NestedStruct,
|
||||||
"b": b.FieldB,
|
"b": b.FieldB,
|
||||||
})
|
})
|
||||||
@ -1912,7 +1954,7 @@ func GetDataB(c *gin.Context) {
|
|||||||
func GetDataC(c *gin.Context) {
|
func GetDataC(c *gin.Context) {
|
||||||
var b StructC
|
var b StructC
|
||||||
c.Bind(&b)
|
c.Bind(&b)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"a": b.NestedStructPointer,
|
"a": b.NestedStructPointer,
|
||||||
"c": b.FieldC,
|
"c": b.FieldC,
|
||||||
})
|
})
|
||||||
@ -1921,7 +1963,7 @@ func GetDataC(c *gin.Context) {
|
|||||||
func GetDataD(c *gin.Context) {
|
func GetDataD(c *gin.Context) {
|
||||||
var b StructD
|
var b StructD
|
||||||
c.Bind(&b)
|
c.Bind(&b)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"x": b.NestedAnonyStruct,
|
"x": b.NestedAnonyStruct,
|
||||||
"d": b.FieldD,
|
"d": b.FieldD,
|
||||||
})
|
})
|
||||||
@ -1984,7 +2026,7 @@ func SomeHandler(c *gin.Context) {
|
|||||||
objA := formA{}
|
objA := formA{}
|
||||||
objB := formB{}
|
objB := formB{}
|
||||||
// This reads c.Request.Body and stores the result into the context.
|
// This reads c.Request.Body and stores the result into the context.
|
||||||
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
|
if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
|
||||||
c.String(http.StatusOK, `the body should be formA`)
|
c.String(http.StatusOK, `the body should be formA`)
|
||||||
// At this time, it reuses body stored in the context.
|
// At this time, it reuses body stored in the context.
|
||||||
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
|
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
|
||||||
@ -2006,6 +2048,61 @@ enough to call binding at once.
|
|||||||
can be called by `c.ShouldBind()` multiple times without any damage to
|
can be called by `c.ShouldBind()` multiple times without any damage to
|
||||||
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
|
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
|
||||||
|
|
||||||
|
### Bind form-data request with custom struct and custom tag
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
customerTag = "url"
|
||||||
|
defaultMemory = 32 << 20
|
||||||
|
)
|
||||||
|
|
||||||
|
type customerBinding struct {}
|
||||||
|
|
||||||
|
func (customerBinding) Name() string {
|
||||||
|
return "form"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (customerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
|
if err != http.ErrNotMultipart {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(obj interface{}) error {
|
||||||
|
if binding.Validator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return binding.Validator.ValidateStruct(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can do this!!!
|
||||||
|
// FormA is a external type that we can't modify it's tag
|
||||||
|
type FormA struct {
|
||||||
|
FieldA string `url:"field_a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListHandler(s *Service) func(ctx *gin.Context) {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
var urlBinding = customerBinding{}
|
||||||
|
var opt FormA
|
||||||
|
err := ctx.MustBindWith(&opt, urlBinding)
|
||||||
|
if err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### http2 server push
|
### http2 server push
|
||||||
|
|
||||||
http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
|
http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
|
||||||
@ -2016,6 +2113,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -2044,7 +2142,7 @@ func main() {
|
|||||||
log.Printf("Failed to push: %v", err)
|
log.Printf("Failed to push: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.HTML(200, "https", gin.H{
|
c.HTML(http.StatusOK, "https", gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2125,6 +2223,73 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Don't trust all proxies
|
||||||
|
|
||||||
|
Gin lets you specify which headers to hold the real client IP (if any),
|
||||||
|
as well as specifying which proxies (or direct clients) you trust to
|
||||||
|
specify one of these headers.
|
||||||
|
|
||||||
|
Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
|
||||||
|
or network CIDRs from where clients which their request headers related to client
|
||||||
|
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||||
|
IPv6 CIDRs.
|
||||||
|
|
||||||
|
**Attention:** Gin trust all proxies by default if you don't specify a trusted
|
||||||
|
proxy using the function above, **this is NOT safe**. At the same time, if you don't
|
||||||
|
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
|
||||||
|
then `Context.ClientIP()` will return the remote address directly to avoid some
|
||||||
|
unnecessary computation.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
router.SetTrustedProxies([]string{"192.168.1.2"})
|
||||||
|
|
||||||
|
router.GET("/", func(c *gin.Context) {
|
||||||
|
// If the client is 192.168.1.2, use the X-Forwarded-For
|
||||||
|
// header to deduce the original client IP from the trust-
|
||||||
|
// worthy parts of that header.
|
||||||
|
// Otherwise, simply return the direct client IP
|
||||||
|
fmt.Printf("ClientIP: %s\n", c.ClientIP())
|
||||||
|
})
|
||||||
|
router.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
|
||||||
|
to skip TrustedProxies check, it has a higher priority than TrustedProxies.
|
||||||
|
Look at the example below:
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
// Use predefined header gin.PlatformXXX
|
||||||
|
router.TrustedPlatform = gin.PlatformGoogleAppEngine
|
||||||
|
// Or set your own trusted request header for another trusted proxy service
|
||||||
|
// Don't set it to any suspect request header, it's unsafe
|
||||||
|
router.TrustedPlatform = "X-CDN-IP"
|
||||||
|
|
||||||
|
router.GET("/", func(c *gin.Context) {
|
||||||
|
// If you set TrustedPlatform, ClientIP() will resolve the
|
||||||
|
// corresponding header and return IP directly
|
||||||
|
fmt.Printf("ClientIP: %s\n", c.ClientIP())
|
||||||
|
})
|
||||||
|
router.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@ -2133,10 +2298,16 @@ The `net/http/httptest` package is preferable way for HTTP testing.
|
|||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func setupRouter() *gin.Engine {
|
func setupRouter() *gin.Engine {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -2164,10 +2335,10 @@ func TestPingRoute(t *testing.T) {
|
|||||||
router := setupRouter()
|
router := setupRouter()
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "pong", w.Body.String())
|
assert.Equal(t, "pong", w.Body.String())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -2183,3 +2354,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
|||||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||||
|
|
||||||
|
|||||||
10
any.go
Normal file
10
any.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
type any = interface{}
|
||||||
3
auth.go
3
auth.go
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -30,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
for _, pair := range a {
|
for _, pair := range a {
|
||||||
if pair.value == authValue {
|
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
|
||||||
return pair.user, true
|
return pair.user, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
binding/any.go
Normal file
10
binding/any.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
type any = interface{}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
@ -21,6 +22,7 @@ const (
|
|||||||
MIMEMSGPACK = "application/x-msgpack"
|
MIMEMSGPACK = "application/x-msgpack"
|
||||||
MIMEMSGPACK2 = "application/msgpack"
|
MIMEMSGPACK2 = "application/msgpack"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Binding describes the interface which needs to be implemented for binding the
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
@ -28,42 +30,43 @@ const (
|
|||||||
// the form POST.
|
// the form POST.
|
||||||
type Binding interface {
|
type Binding interface {
|
||||||
Name() string
|
Name() string
|
||||||
Bind(*http.Request, interface{}) error
|
Bind(*http.Request, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||||
// but it reads the body from supplied bytes instead of req.Body.
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
type BindingBody interface {
|
type BindingBody interface {
|
||||||
Binding
|
Binding
|
||||||
BindBody([]byte, interface{}) error
|
BindBody([]byte, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||||
// but it read the Params.
|
// but it reads the Params.
|
||||||
type BindingUri interface {
|
type BindingUri interface {
|
||||||
Name() string
|
Name() string
|
||||||
BindUri(map[string][]string, interface{}) error
|
BindUri(map[string][]string, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||||
|
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
|
||||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(any) error
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the
|
// Engine returns the underlying validator engine which powers the
|
||||||
// StructValidator implementation.
|
// StructValidator implementation.
|
||||||
Engine() interface{}
|
Engine() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator is the default validator which implements the StructValidator
|
// Validator is the default validator which implements the StructValidator
|
||||||
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
// under the hood.
|
// under the hood.
|
||||||
var Validator StructValidator = &defaultValidator{}
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
@ -81,6 +84,7 @@ var (
|
|||||||
YAML = yamlBinding{}
|
YAML = yamlBinding{}
|
||||||
Uri = uriBinding{}
|
Uri = uriBinding{}
|
||||||
Header = headerBinding{}
|
Header = headerBinding{}
|
||||||
|
TOML = tomlBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
@ -101,6 +105,8 @@ func Default(method, contentType string) Binding {
|
|||||||
return MsgPack
|
return MsgPack
|
||||||
case MIMEYAML:
|
case MIMEYAML:
|
||||||
return YAML
|
return YAML
|
||||||
|
case MIMETOML:
|
||||||
|
return TOML
|
||||||
case MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
return FormMultipart
|
return FormMultipart
|
||||||
default: // case MIMEPOSTForm:
|
default: // case MIMEPOSTForm:
|
||||||
@ -108,7 +114,7 @@ func Default(method, contentType string) Binding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(obj interface{}) error {
|
func validate(obj any) error {
|
||||||
if Validator == nil {
|
if Validator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build nomsgpack
|
||||||
// +build nomsgpack
|
// +build nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
@ -19,6 +20,7 @@ const (
|
|||||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
MIMEPROTOBUF = "application/x-protobuf"
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Binding describes the interface which needs to be implemented for binding the
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
@ -26,42 +28,42 @@ const (
|
|||||||
// the form POST.
|
// the form POST.
|
||||||
type Binding interface {
|
type Binding interface {
|
||||||
Name() string
|
Name() string
|
||||||
Bind(*http.Request, interface{}) error
|
Bind(*http.Request, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||||
// but it reads the body from supplied bytes instead of req.Body.
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
type BindingBody interface {
|
type BindingBody interface {
|
||||||
Binding
|
Binding
|
||||||
BindBody([]byte, interface{}) error
|
BindBody([]byte, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||||
// but it read the Params.
|
// but it reads the Params.
|
||||||
type BindingUri interface {
|
type BindingUri interface {
|
||||||
Name() string
|
Name() string
|
||||||
BindUri(map[string][]string, interface{}) error
|
BindUri(map[string][]string, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(any) error
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the
|
// Engine returns the underlying validator engine which powers the
|
||||||
// StructValidator implementation.
|
// StructValidator implementation.
|
||||||
Engine() interface{}
|
Engine() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator is the default validator which implements the StructValidator
|
// Validator is the default validator which implements the StructValidator
|
||||||
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
// under the hood.
|
// under the hood.
|
||||||
var Validator StructValidator = &defaultValidator{}
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
@ -78,6 +80,7 @@ var (
|
|||||||
YAML = yamlBinding{}
|
YAML = yamlBinding{}
|
||||||
Uri = uriBinding{}
|
Uri = uriBinding{}
|
||||||
Header = headerBinding{}
|
Header = headerBinding{}
|
||||||
|
TOML = tomlBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
@ -98,12 +101,14 @@ func Default(method, contentType string) Binding {
|
|||||||
return YAML
|
return YAML
|
||||||
case MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
return FormMultipart
|
return FormMultipart
|
||||||
|
case MIMETOML:
|
||||||
|
return TOML
|
||||||
default: // case MIMEPOSTForm:
|
default: // case MIMEPOSTForm:
|
||||||
return Form
|
return Form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(obj interface{}) error {
|
func validate(obj any) error {
|
||||||
if Validator == nil {
|
if Validator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type appkey struct {
|
type appkey struct {
|
||||||
@ -35,7 +35,7 @@ type QueryTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStruct struct {
|
type FooStruct struct {
|
||||||
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooBarStruct struct {
|
type FooBarStruct struct {
|
||||||
@ -61,11 +61,11 @@ type FooDefaultBarStruct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStructUseNumber struct {
|
type FooStructUseNumber struct {
|
||||||
Foo interface{} `json:"foo" binding:"required"`
|
Foo any `json:"foo" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooStructDisallowUnknownFields struct {
|
type FooStructDisallowUnknownFields struct {
|
||||||
Foo interface{} `json:"foo" binding:"required"`
|
Foo any `json:"foo" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooBarStructForTimeType struct {
|
type FooBarStructForTimeType struct {
|
||||||
@ -93,7 +93,7 @@ type FooStructForTimeTypeFailLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForMapType struct {
|
type FooStructForMapType struct {
|
||||||
MapFoo map[string]interface{} `form:"map_foo"`
|
MapFoo map[string]any `form:"map_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForIgnoreFormTag struct {
|
type FooStructForIgnoreFormTag struct {
|
||||||
@ -106,7 +106,7 @@ type InvalidNameType struct {
|
|||||||
|
|
||||||
type InvalidNameMapType struct {
|
type InvalidNameMapType struct {
|
||||||
TestName struct {
|
TestName struct {
|
||||||
MapFoo map[string]interface{} `form:"map_foo"`
|
MapFoo map[string]any `form:"map_foo"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ type FooStructForStructPointerType struct {
|
|||||||
|
|
||||||
type FooStructForSliceMapType struct {
|
type FooStructForSliceMapType struct {
|
||||||
// Unknown type: not support map
|
// Unknown type: not support map
|
||||||
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
|
SliceMapFoo []map[string]any `form:"slice_map_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForBoolType struct {
|
type FooStructForBoolType struct {
|
||||||
@ -141,7 +141,7 @@ type FooStructForStringPtrType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForMapPtrType struct {
|
type FooStructForMapPtrType struct {
|
||||||
PtrBar *map[string]interface{} `form:"ptr_bar"`
|
PtrBar *map[string]any `form:"ptr_bar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefault(t *testing.T) {
|
func TestBindingDefault(t *testing.T) {
|
||||||
@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
||||||
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
||||||
|
|
||||||
|
assert.Equal(t, TOML, Default("POST", MIMETOML))
|
||||||
|
assert.Equal(t, TOML, Default("PUT", MIMETOML))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingJSONNilBody(t *testing.T) {
|
func TestBindingJSONNilBody(t *testing.T) {
|
||||||
@ -181,6 +184,20 @@ func TestBindingJSON(t *testing.T) {
|
|||||||
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingJSONSlice(t *testing.T) {
|
||||||
|
EnableDecoderDisallowUnknownFields = true
|
||||||
|
defer func() {
|
||||||
|
EnableDecoderDisallowUnknownFields = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": ""}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingJSONUseNumber(t *testing.T) {
|
func TestBindingJSONUseNumber(t *testing.T) {
|
||||||
testBodyBindingUseNumber(t,
|
testBodyBindingUseNumber(t,
|
||||||
JSON, "json",
|
JSON, "json",
|
||||||
@ -440,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) {
|
|||||||
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
|
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingTOML(t *testing.T) {
|
||||||
|
testBodyBinding(t,
|
||||||
|
TOML, "toml",
|
||||||
|
"/", "/",
|
||||||
|
`foo="bar"`, `bar="foo"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingTOMLFail(t *testing.T) {
|
||||||
|
testBodyBindingFail(t,
|
||||||
|
TOML, "toml",
|
||||||
|
"/", "/",
|
||||||
|
`foo=\n"bar"`, `bar="foo"`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingYAML(t *testing.T) {
|
func TestBindingYAML(t *testing.T) {
|
||||||
testBodyBinding(t,
|
testBodyBinding(t,
|
||||||
YAML, "yaml",
|
YAML, "yaml",
|
||||||
@ -754,7 +785,7 @@ func TestHeaderBinding(t *testing.T) {
|
|||||||
req.Header.Add("fail", `{fail:fail}`)
|
req.Header.Add("fail", `{fail:fail}`)
|
||||||
|
|
||||||
type failStruct struct {
|
type failStruct struct {
|
||||||
Fail map[string]interface{} `header:"fail"`
|
Fail map[string]any `header:"fail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.Bind(req, &failStruct{})
|
err := h.Bind(req, &failStruct{})
|
||||||
@ -775,11 +806,11 @@ func TestUriBinding(t *testing.T) {
|
|||||||
assert.Equal(t, "thinkerou", tag.Name)
|
assert.Equal(t, "thinkerou", tag.Name)
|
||||||
|
|
||||||
type NotSupportStruct struct {
|
type NotSupportStruct struct {
|
||||||
Name map[string]interface{} `uri:"name"`
|
Name map[string]any `uri:"name"`
|
||||||
}
|
}
|
||||||
var not NotSupportStruct
|
var not NotSupportStruct
|
||||||
assert.Error(t, b.BindUri(m, ¬))
|
assert.Error(t, b.BindUri(m, ¬))
|
||||||
assert.Equal(t, map[string]interface{}(nil), not.Name)
|
assert.Equal(t, map[string]any(nil), not.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUriInnerBinding(t *testing.T) {
|
func TestUriInnerBinding(t *testing.T) {
|
||||||
@ -818,7 +849,6 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba
|
|||||||
assert.Equal(t, 1, obj.Page)
|
assert.Equal(t, 1, obj.Page)
|
||||||
assert.Equal(t, 2, obj.Size)
|
assert.Equal(t, 2, obj.Size)
|
||||||
assert.Equal(t, "test-appkey", obj.Appkey)
|
assert.Equal(t, "test-appkey", obj.Appkey)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
||||||
@ -1181,6 +1211,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||||
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
|
var obj1 []FooStruct
|
||||||
|
req := requestWithBody("POST", path, body)
|
||||||
|
err := b.Bind(req, &obj1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var obj2 []FooStruct
|
||||||
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
|
err = JSON.Bind(req, &obj2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
||||||
obj := make(map[string]string)
|
obj := make(map[string]string)
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody("POST", path, body)
|
||||||
@ -1312,6 +1356,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
invalidobj := FooStruct{}
|
||||||
|
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
|
||||||
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
|
err = b.Bind(req, &invalidobj)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "obj is not ProtoMessage")
|
||||||
|
|
||||||
obj = protoexample.Test{}
|
obj = protoexample.Test{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@ -16,29 +18,73 @@ type defaultValidator struct {
|
|||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SliceValidationError []error
|
||||||
|
|
||||||
|
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
|
||||||
|
func (err SliceValidationError) Error() string {
|
||||||
|
n := len(err)
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
var b strings.Builder
|
||||||
|
if err[0] != nil {
|
||||||
|
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
|
||||||
|
}
|
||||||
|
if n > 1 {
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
if err[i] != nil {
|
||||||
|
b.WriteString("\n")
|
||||||
|
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
var _ StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
func (v *defaultValidator) ValidateStruct(obj any) error {
|
||||||
value := reflect.ValueOf(obj)
|
if obj == nil {
|
||||||
valueType := value.Kind()
|
|
||||||
if valueType == reflect.Ptr {
|
|
||||||
valueType = value.Elem().Kind()
|
|
||||||
}
|
|
||||||
if valueType == reflect.Struct {
|
|
||||||
v.lazyinit()
|
|
||||||
if err := v.validate.Struct(obj); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(obj)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return v.ValidateStruct(value.Elem().Interface())
|
||||||
|
case reflect.Struct:
|
||||||
|
return v.validateStruct(obj)
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
count := value.Len()
|
||||||
|
validateRet := make(SliceValidationError, 0)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||||
|
validateRet = append(validateRet, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(validateRet) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return validateRet
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStruct receives struct type
|
||||||
|
func (v *defaultValidator) validateStruct(obj any) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.Struct(obj)
|
||||||
|
}
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the default
|
// Engine returns the underlying validator engine which powers the default
|
||||||
// Validator instance. This is useful if you want to register custom validations
|
// Validator instance. This is useful if you want to register custom validations
|
||||||
// or struct level validations. See validator GoDoc for more info -
|
// or struct level validations. See validator GoDoc for more info -
|
||||||
// https://godoc.org/gopkg.in/go-playground/validator.v8
|
// https://pkg.go.dev/github.com/go-playground/validator/v10
|
||||||
func (v *defaultValidator) Engine() interface{} {
|
func (v *defaultValidator) Engine() any {
|
||||||
v.lazyinit()
|
v.lazyinit()
|
||||||
return v.validate
|
return v.validate
|
||||||
}
|
}
|
||||||
|
|||||||
24
binding/default_validator_benchmark_test.go
Normal file
24
binding/default_validator_benchmark_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkSliceValidationError(b *testing.B) {
|
||||||
|
const size int = 100
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
e := make(SliceValidationError, size)
|
||||||
|
for j := 0; j < size; j++ {
|
||||||
|
e[j] = errors.New(strconv.Itoa(j))
|
||||||
|
}
|
||||||
|
if len(e.Error()) == 0 {
|
||||||
|
b.Errorf("error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
binding/default_validator_test.go
Normal file
88
binding/default_validator_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2020 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSliceValidationError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err SliceValidationError
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"},
|
||||||
|
{"has zero elements", SliceValidationError{}, ""},
|
||||||
|
{"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"},
|
||||||
|
{"has two elements",
|
||||||
|
SliceValidationError{
|
||||||
|
errors.New("first error"),
|
||||||
|
errors.New("second error"),
|
||||||
|
},
|
||||||
|
"[0]: first error\n[1]: second error",
|
||||||
|
},
|
||||||
|
{"has many elements",
|
||||||
|
SliceValidationError{
|
||||||
|
errors.New("first error"),
|
||||||
|
errors.New("second error"),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
errors.New("last error"),
|
||||||
|
},
|
||||||
|
"[0]: first error\n[1]: second error\n[5]: last error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.err.Error(); got != tt.want {
|
||||||
|
t.Errorf("SliceValidationError.Error() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultValidator(t *testing.T) {
|
||||||
|
type exampleStruct struct {
|
||||||
|
A string `binding:"max=8"`
|
||||||
|
B int `binding:"gt=0"`
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
v *defaultValidator
|
||||||
|
obj any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"validate nil obj", &defaultValidator{}, nil, false},
|
||||||
|
{"validate int obj", &defaultValidator{}, 3, false},
|
||||||
|
{"validate struct failed-1", &defaultValidator{}, exampleStruct{A: "123456789", B: 1}, true},
|
||||||
|
{"validate struct failed-2", &defaultValidator{}, exampleStruct{A: "12345678", B: 0}, true},
|
||||||
|
{"validate struct passed", &defaultValidator{}, exampleStruct{A: "12345678", B: 1}, false},
|
||||||
|
{"validate *struct failed-1", &defaultValidator{}, &exampleStruct{A: "123456789", B: 1}, true},
|
||||||
|
{"validate *struct failed-2", &defaultValidator{}, &exampleStruct{A: "12345678", B: 0}, true},
|
||||||
|
{"validate *struct passed", &defaultValidator{}, &exampleStruct{A: "12345678", B: 1}, false},
|
||||||
|
{"validate []struct failed-1", &defaultValidator{}, []exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate []struct failed-2", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate []struct passed", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate []*struct failed-1", &defaultValidator{}, []*exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate []*struct failed-2", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate []*struct passed", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *[]struct failed-1", &defaultValidator{}, &[]exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *[]struct failed-2", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *[]struct passed", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("defaultValidator.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,15 +19,13 @@ func (formBinding) Name() string {
|
|||||||
return "form"
|
return "form"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
func (formBinding) Bind(req *http.Request, obj any) error {
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
|
||||||
if err != http.ErrNotMultipart {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err := mapForm(obj, req.Form); err != nil {
|
if err := mapForm(obj, req.Form); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ func (formPostBinding) Name() string {
|
|||||||
return "form-urlencoded"
|
return "form-urlencoded"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
|
func (formPostBinding) Bind(req *http.Request, obj any) error {
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -51,7 +50,7 @@ func (formMultipartBinding) Name() string {
|
|||||||
return "multipart/form-data"
|
return "multipart/form-data"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
func (formMultipartBinding) Bind(req *http.Request, obj any) error {
|
||||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,22 +16,34 @@ import (
|
|||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errUnknownType = errors.New("unknown type")
|
var (
|
||||||
|
errUnknownType = errors.New("unknown type")
|
||||||
|
|
||||||
func mapUri(ptr interface{}, m map[string][]string) error {
|
// ErrConvertMapStringSlice can not covert to map[string][]string
|
||||||
|
ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
|
||||||
|
|
||||||
|
// ErrConvertToMapString can not convert to map[string]string
|
||||||
|
ErrConvertToMapString = errors.New("can not convert to map of strings")
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapURI(ptr any, m map[string][]string) error {
|
||||||
return mapFormByTag(ptr, m, "uri")
|
return mapFormByTag(ptr, m, "uri")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapForm(ptr interface{}, form map[string][]string) error {
|
func mapForm(ptr any, form map[string][]string) error {
|
||||||
return mapFormByTag(ptr, form, "form")
|
return mapFormByTag(ptr, form, "form")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
|
||||||
|
return mapFormByTag(ptr, form, tag)
|
||||||
|
}
|
||||||
|
|
||||||
var emptyField = reflect.StructField{}
|
var emptyField = reflect.StructField{}
|
||||||
|
|
||||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
func mapFormByTag(ptr any, form map[string][]string, tag string) error {
|
||||||
// Check if ptr is a map
|
// Check if ptr is a map
|
||||||
ptrVal := reflect.ValueOf(ptr)
|
ptrVal := reflect.ValueOf(ptr)
|
||||||
var pointed interface{}
|
var pointed any
|
||||||
if ptrVal.Kind() == reflect.Ptr {
|
if ptrVal.Kind() == reflect.Ptr {
|
||||||
ptrVal = ptrVal.Elem()
|
ptrVal = ptrVal.Elem()
|
||||||
pointed = ptrVal.Interface()
|
pointed = ptrVal.Interface()
|
||||||
@ -49,7 +61,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
|||||||
|
|
||||||
// setter tries to set value on a walking by fields of a struct
|
// setter tries to set value on a walking by fields of a struct
|
||||||
type setter interface {
|
type setter interface {
|
||||||
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type formSource map[string][]string
|
type formSource map[string][]string
|
||||||
@ -57,11 +69,11 @@ type formSource map[string][]string
|
|||||||
var _ setter = formSource(nil)
|
var _ setter = formSource(nil)
|
||||||
|
|
||||||
// TrySet tries to set a value by request's form source (like map[string][]string)
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||||
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
return setByForm(value, field, form, tagValue, opt)
|
return setByForm(value, field, form, tagValue, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mappingByPtr(ptr interface{}, setter setter, tag string) error {
|
func mappingByPtr(ptr any, setter setter, tag string) error {
|
||||||
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -71,7 +83,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var vKind = value.Kind()
|
vKind := value.Kind()
|
||||||
|
|
||||||
if vKind == reflect.Ptr {
|
if vKind == reflect.Ptr {
|
||||||
var isNew bool
|
var isNew bool
|
||||||
@ -80,14 +92,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
|||||||
isNew = true
|
isNew = true
|
||||||
vPtr = reflect.New(value.Type().Elem())
|
vPtr = reflect.New(value.Type().Elem())
|
||||||
}
|
}
|
||||||
isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
|
isSet, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if isNew && isSetted {
|
if isNew && isSet {
|
||||||
value.Set(vPtr)
|
value.Set(vPtr)
|
||||||
}
|
}
|
||||||
return isSetted, nil
|
return isSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if vKind != reflect.Struct || !field.Anonymous {
|
if vKind != reflect.Struct || !field.Anonymous {
|
||||||
@ -103,19 +115,19 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
|||||||
if vKind == reflect.Struct {
|
if vKind == reflect.Struct {
|
||||||
tValue := value.Type()
|
tValue := value.Type()
|
||||||
|
|
||||||
var isSetted bool
|
var isSet bool
|
||||||
for i := 0; i < value.NumField(); i++ {
|
for i := 0; i < value.NumField(); i++ {
|
||||||
sf := tValue.Field(i)
|
sf := tValue.Field(i)
|
||||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
ok, err := mapping(value.Field(i), sf, setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
isSetted = isSetted || ok
|
isSet = isSet || ok
|
||||||
}
|
}
|
||||||
return isSetted, nil
|
return isSet, nil
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -152,7 +164,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
|||||||
return setter.TrySet(value, field, tagValue, setOpt)
|
return setter.TrySet(value, field, tagValue, setOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
vs, ok := form[tagValue]
|
vs, ok := form[tagValue]
|
||||||
if !ok && !opt.isDefaultExists {
|
if !ok && !opt.isDefaultExists {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -198,7 +210,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
|||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
switch value.Interface().(type) {
|
switch value.Interface().(type) {
|
||||||
case time.Duration:
|
case time.Duration:
|
||||||
return setTimeDuration(val, value, field)
|
return setTimeDuration(val, value)
|
||||||
}
|
}
|
||||||
return setIntField(val, 64, value)
|
return setIntField(val, 64, value)
|
||||||
case reflect.Uint:
|
case reflect.Uint:
|
||||||
@ -298,7 +310,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
t := time.Unix(tv/int64(d), tv%int64(d))
|
t := time.Unix(tv/int64(d), tv%int64(d))
|
||||||
value.Set(reflect.ValueOf(t))
|
value.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val == "" {
|
if val == "" {
|
||||||
@ -348,7 +359,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
|
func setTimeDuration(val string, value reflect.Value) error {
|
||||||
d, err := time.ParseDuration(val)
|
d, err := time.ParseDuration(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -365,13 +376,13 @@ func head(str, sep string) (head string, tail string) {
|
|||||||
return str[:idx], str[idx+len(sep):]
|
return str[:idx], str[idx+len(sep):]
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFormMap(ptr interface{}, form map[string][]string) error {
|
func setFormMap(ptr any, form map[string][]string) error {
|
||||||
el := reflect.TypeOf(ptr).Elem()
|
el := reflect.TypeOf(ptr).Elem()
|
||||||
|
|
||||||
if el.Kind() == reflect.Slice {
|
if el.Kind() == reflect.Slice {
|
||||||
ptrMap, ok := ptr.(map[string][]string)
|
ptrMap, ok := ptr.(map[string][]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("cannot convert to map slices of strings")
|
return ErrConvertMapStringSlice
|
||||||
}
|
}
|
||||||
for k, v := range form {
|
for k, v := range form {
|
||||||
ptrMap[k] = v
|
ptrMap[k] = v
|
||||||
@ -382,7 +393,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
|
|||||||
|
|
||||||
ptrMap, ok := ptr.(map[string]string)
|
ptrMap, ok := ptr.(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("cannot convert to map of strings")
|
return ErrConvertToMapString
|
||||||
}
|
}
|
||||||
for k, v := range form {
|
for k, v := range form {
|
||||||
ptrMap[k] = v[len(v)-1] // pick last
|
ptrMap[k] = v[len(v)-1] // pick last
|
||||||
|
|||||||
@ -18,9 +18,9 @@ func TestMappingBaseTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
value interface{}
|
value any
|
||||||
form string
|
form string
|
||||||
expect interface{}
|
expect any
|
||||||
}{
|
}{
|
||||||
{"base type", struct{ F int }{}, "9", int(9)},
|
{"base type", struct{ F int }{}, "9", int(9)},
|
||||||
{"base type", struct{ F int8 }{}, "9", int8(9)},
|
{"base type", struct{ F int8 }{}, "9", int8(9)},
|
||||||
@ -131,7 +131,7 @@ func TestMappingURI(t *testing.T) {
|
|||||||
var s struct {
|
var s struct {
|
||||||
F int `uri:"field"`
|
F int `uri:"field"`
|
||||||
}
|
}
|
||||||
err := mapUri(&s, map[string][]string{"field": {"6"}})
|
err := mapURI(&s, map[string][]string{"field": {"6"}})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int(6), s.F)
|
assert.Equal(t, int(6), s.F)
|
||||||
}
|
}
|
||||||
@ -145,6 +145,15 @@ func TestMappingForm(t *testing.T) {
|
|||||||
assert.Equal(t, int(6), s.F)
|
assert.Equal(t, int(6), s.F)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapFormWithTag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
F int `externalTag:"field"`
|
||||||
|
}
|
||||||
|
err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(6), s.F)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMappingTime(t *testing.T) {
|
func TestMappingTime(t *testing.T) {
|
||||||
var s struct {
|
var s struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -12,7 +16,7 @@ func (headerBinding) Name() string {
|
|||||||
return "header"
|
return "header"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (headerBinding) Bind(req *http.Request, obj interface{}) error {
|
func (headerBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
|
||||||
if err := mapHeader(obj, req.Header); err != nil {
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -21,7 +25,7 @@ func (headerBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
return validate(obj)
|
return validate(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapHeader(ptr interface{}, h map[string][]string) error {
|
func mapHeader(ptr any, h map[string][]string) error {
|
||||||
return mappingByPtr(ptr, headerSource(h), "header")
|
return mappingByPtr(ptr, headerSource(h), "header")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +33,6 @@ type headerSource map[string][]string
|
|||||||
|
|
||||||
var _ setter = headerSource(nil)
|
var _ setter = headerSource(nil)
|
||||||
|
|
||||||
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {
|
||||||
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
|
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -30,18 +30,18 @@ func (jsonBinding) Name() string {
|
|||||||
return "json"
|
return "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
func (jsonBinding) Bind(req *http.Request, obj any) error {
|
||||||
if req == nil || req.Body == nil {
|
if req == nil || req.Body == nil {
|
||||||
return fmt.Errorf("invalid request")
|
return errors.New("invalid request")
|
||||||
}
|
}
|
||||||
return decodeJSON(req.Body, obj)
|
return decodeJSON(req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jsonBinding) BindBody(body []byte, obj interface{}) error {
|
func (jsonBinding) BindBody(body []byte, obj any) error {
|
||||||
return decodeJSON(bytes.NewReader(body), obj)
|
return decodeJSON(bytes.NewReader(body), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeJSON(r io.Reader, obj interface{}) error {
|
func decodeJSON(r io.Reader, obj any) error {
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.NewDecoder(r)
|
||||||
if EnableDecoderUseNumber {
|
if EnableDecoderUseNumber {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
@ -20,15 +21,15 @@ func (msgpackBinding) Name() string {
|
|||||||
return "msgpack"
|
return "msgpack"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
|
func (msgpackBinding) Bind(req *http.Request, obj any) error {
|
||||||
return decodeMsgPack(req.Body, obj)
|
return decodeMsgPack(req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
|
func (msgpackBinding) BindBody(body []byte, obj any) error {
|
||||||
return decodeMsgPack(bytes.NewReader(body), obj)
|
return decodeMsgPack(bytes.NewReader(body), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMsgPack(r io.Reader, obj interface{}) error {
|
func decodeMsgPack(r io.Reader, obj any) error {
|
||||||
cdc := new(codec.MsgpackHandle)
|
cdc := new(codec.MsgpackHandle)
|
||||||
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
@ -25,7 +26,7 @@ func TestMsgpackBindingBindBody(t *testing.T) {
|
|||||||
assert.Equal(t, "FOO", s.Foo)
|
assert.Equal(t, "FOO", s.Foo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func msgpackBody(t *testing.T, obj interface{}) []byte {
|
func msgpackBody(t *testing.T, obj any) []byte {
|
||||||
var bs bytes.Buffer
|
var bs bytes.Buffer
|
||||||
h := &codec.MsgpackHandle{}
|
h := &codec.MsgpackHandle{}
|
||||||
err := codec.NewEncoder(&bs, h).Encode(obj)
|
err := codec.NewEncoder(&bs, h).Encode(obj)
|
||||||
|
|||||||
@ -15,8 +15,16 @@ type multipartRequest http.Request
|
|||||||
|
|
||||||
var _ setter = (*multipartRequest)(nil)
|
var _ setter = (*multipartRequest)(nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMultiFileHeader multipart.FileHeader invalid
|
||||||
|
ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader")
|
||||||
|
|
||||||
|
// ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid
|
||||||
|
ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader")
|
||||||
|
)
|
||||||
|
|
||||||
// TrySet tries to set a value by the multipart request with the binding a form file
|
// TrySet tries to set a value by the multipart request with the binding a form file
|
||||||
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
|
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {
|
||||||
if files := r.MultipartForm.File[key]; len(files) != 0 {
|
if files := r.MultipartForm.File[key]; len(files) != 0 {
|
||||||
return setByMultipartFormFile(value, field, files)
|
return setByMultipartFormFile(value, field, files)
|
||||||
}
|
}
|
||||||
@ -24,7 +32,7 @@ func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField
|
|||||||
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
switch value.Interface().(type) {
|
switch value.Interface().(type) {
|
||||||
@ -40,26 +48,26 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
|
|||||||
}
|
}
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
|
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
|
||||||
isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)
|
isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||||
if err != nil || !isSetted {
|
if err != nil || !isSet {
|
||||||
return isSetted, err
|
return isSet, err
|
||||||
}
|
}
|
||||||
value.Set(slice)
|
value.Set(slice)
|
||||||
return true, nil
|
return true, nil
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
return setArrayOfMultipartFormFiles(value, field, files)
|
return setArrayOfMultipartFormFiles(value, field, files)
|
||||||
}
|
}
|
||||||
return false, errors.New("unsupported field type for multipart.FileHeader")
|
return false, ErrMultiFileHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||||
if value.Len() != len(files) {
|
if value.Len() != len(files) {
|
||||||
return false, errors.New("unsupported len of array for []*multipart.FileHeader")
|
return false, ErrMultiFileHeaderLenInvalid
|
||||||
}
|
}
|
||||||
for i := range files {
|
for i := range files {
|
||||||
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||||
if err != nil || !setted {
|
if err != nil || !set {
|
||||||
return setted, err
|
return set, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
@ -76,7 +76,7 @@ func TestFormMultipartBindingBindError(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
s interface{}
|
s any
|
||||||
}{
|
}{
|
||||||
{"wrong type", &struct {
|
{"wrong type", &struct {
|
||||||
Files int `form:"file"`
|
Files int `form:"file"`
|
||||||
@ -124,7 +124,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
|
|||||||
|
|
||||||
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
|
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
|
||||||
assert.Equal(t, file.Filename, fh.Filename)
|
assert.Equal(t, file.Filename, fh.Filename)
|
||||||
// assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8
|
assert.Equal(t, int64(len(file.Content)), fh.Size)
|
||||||
|
|
||||||
fl, err := fh.Open()
|
fl, err := fh.Open()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@ -5,10 +5,11 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type protobufBinding struct{}
|
type protobufBinding struct{}
|
||||||
@ -17,7 +18,7 @@ func (protobufBinding) Name() string {
|
|||||||
return "protobuf"
|
return "protobuf"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b protobufBinding) Bind(req *http.Request, obj any) error {
|
||||||
buf, err := ioutil.ReadAll(req.Body)
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -25,8 +26,12 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
return b.BindBody(buf, obj)
|
return b.BindBody(buf, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (protobufBinding) BindBody(body []byte, obj interface{}) error {
|
func (protobufBinding) BindBody(body []byte, obj any) error {
|
||||||
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
|
msg, ok := obj.(proto.Message)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("obj is not ProtoMessage")
|
||||||
|
}
|
||||||
|
if err := proto.Unmarshal(body, msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Here it's same to return validate(obj), but util now we can't add
|
// Here it's same to return validate(obj), but util now we can't add
|
||||||
|
|||||||
@ -12,7 +12,7 @@ func (queryBinding) Name() string {
|
|||||||
return "query"
|
return "query"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (queryBinding) Bind(req *http.Request, obj interface{}) error {
|
func (queryBinding) Bind(req *http.Request, obj any) error {
|
||||||
values := req.URL.Query()
|
values := req.URL.Query()
|
||||||
if err := mapForm(obj, values); err != nil {
|
if err := mapForm(obj, values); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
35
binding/toml.go
Normal file
35
binding/toml.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlBinding struct{}
|
||||||
|
|
||||||
|
func (tomlBinding) Name() string {
|
||||||
|
return "toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeToml(r io.Reader, obj any) error {
|
||||||
|
decoder := toml.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decoder.Decode(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeToml(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeToml(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
22
binding/toml_test.go
Normal file
22
binding/toml_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTOMLBindingBindBody(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Foo string `toml:"foo"`
|
||||||
|
}
|
||||||
|
tomlBody := `foo="FOO"`
|
||||||
|
err := tomlBinding{}.BindBody([]byte(tomlBody), &s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "FOO", s.Foo)
|
||||||
|
}
|
||||||
@ -10,8 +10,8 @@ func (uriBinding) Name() string {
|
|||||||
return "uri"
|
return "uri"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
|
func (uriBinding) BindUri(m map[string][]string, obj any) error {
|
||||||
if err := mapUri(obj, m); err != nil {
|
if err := mapURI(obj, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
|
|||||||
@ -59,7 +59,7 @@ type structNoValidationValues struct {
|
|||||||
StructSlice []substructNoValidation
|
StructSlice []substructNoValidation
|
||||||
InterfaceSlice []testInterface
|
InterfaceSlice []testInterface
|
||||||
|
|
||||||
UniversalInterface interface{}
|
UniversalInterface any
|
||||||
CustomInterface testInterface
|
CustomInterface testInterface
|
||||||
|
|
||||||
FloatMap map[string]float32
|
FloatMap map[string]float32
|
||||||
@ -169,7 +169,7 @@ func TestValidateNoValidationPointers(t *testing.T) {
|
|||||||
//assert.Equal(t, origin, test)
|
//assert.Equal(t, origin, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Object map[string]interface{}
|
type Object map[string]any
|
||||||
|
|
||||||
func TestValidatePrimitives(t *testing.T) {
|
func TestValidatePrimitives(t *testing.T) {
|
||||||
obj := Object{"foo": "bar", "bar": 1}
|
obj := Object{"foo": "bar", "bar": 1}
|
||||||
|
|||||||
@ -17,14 +17,14 @@ func (xmlBinding) Name() string {
|
|||||||
return "xml"
|
return "xml"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
func (xmlBinding) Bind(req *http.Request, obj any) error {
|
||||||
return decodeXML(req.Body, obj)
|
return decodeXML(req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xmlBinding) BindBody(body []byte, obj interface{}) error {
|
func (xmlBinding) BindBody(body []byte, obj any) error {
|
||||||
return decodeXML(bytes.NewReader(body), obj)
|
return decodeXML(bytes.NewReader(body), obj)
|
||||||
}
|
}
|
||||||
func decodeXML(r io.Reader, obj interface{}) error {
|
func decodeXML(r io.Reader, obj any) error {
|
||||||
decoder := xml.NewDecoder(r)
|
decoder := xml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -18,15 +18,15 @@ func (yamlBinding) Name() string {
|
|||||||
return "yaml"
|
return "yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
|
func (yamlBinding) Bind(req *http.Request, obj any) error {
|
||||||
return decodeYAML(req.Body, obj)
|
return decodeYAML(req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yamlBinding) BindBody(body []byte, obj interface{}) error {
|
func (yamlBinding) BindBody(body []byte, obj any) error {
|
||||||
return decodeYAML(bytes.NewReader(body), obj)
|
return decodeYAML(bytes.NewReader(body), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeYAML(r io.Reader, obj interface{}) error {
|
func decodeYAML(r io.Reader, obj any) error {
|
||||||
decoder := yaml.NewDecoder(r)
|
decoder := yaml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
310
context.go
310
context.go
@ -6,9 +6,9 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net"
|
"net"
|
||||||
@ -34,12 +34,17 @@ const (
|
|||||||
MIMEPOSTForm = binding.MIMEPOSTForm
|
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||||
MIMEYAML = binding.MIMEYAML
|
MIMEYAML = binding.MIMEYAML
|
||||||
|
MIMETOML = binding.MIMETOML
|
||||||
)
|
)
|
||||||
|
|
||||||
// BodyBytesKey indicates a default body bytes key.
|
// BodyBytesKey indicates a default body bytes key.
|
||||||
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||||
|
|
||||||
const abortIndex int8 = math.MaxInt8 / 2
|
// ContextKey is the key that a Context returns itself for.
|
||||||
|
const ContextKey = "_gin-gonic/gin/contextkey"
|
||||||
|
|
||||||
|
// abortIndex represents a typical value used in abort functions.
|
||||||
|
const abortIndex int8 = math.MaxInt8 >> 1
|
||||||
|
|
||||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||||
@ -55,12 +60,13 @@ type Context struct {
|
|||||||
|
|
||||||
engine *Engine
|
engine *Engine
|
||||||
params *Params
|
params *Params
|
||||||
|
skippedNodes *[]skippedNode
|
||||||
|
|
||||||
// This mutex protect Keys map
|
// This mutex protects Keys map.
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
// Keys is a key/value pair exclusively for the context of each request.
|
// Keys is a key/value pair exclusively for the context of each request.
|
||||||
Keys map[string]interface{}
|
Keys map[string]any
|
||||||
|
|
||||||
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
|
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
|
||||||
Errors errorMsgs
|
Errors errorMsgs
|
||||||
@ -68,10 +74,10 @@ type Context struct {
|
|||||||
// Accepted defines a list of manually accepted formats for content negotiation.
|
// Accepted defines a list of manually accepted formats for content negotiation.
|
||||||
Accepted []string
|
Accepted []string
|
||||||
|
|
||||||
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
|
// queryCache caches the query result from c.Request.URL.Query().
|
||||||
queryCache url.Values
|
queryCache url.Values
|
||||||
|
|
||||||
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
|
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
|
||||||
// or PUT body parameters.
|
// or PUT body parameters.
|
||||||
formCache url.Values
|
formCache url.Values
|
||||||
|
|
||||||
@ -86,17 +92,19 @@ type Context struct {
|
|||||||
|
|
||||||
func (c *Context) reset() {
|
func (c *Context) reset() {
|
||||||
c.Writer = &c.writermem
|
c.Writer = &c.writermem
|
||||||
c.Params = c.Params[0:0]
|
c.Params = c.Params[:0]
|
||||||
c.handlers = nil
|
c.handlers = nil
|
||||||
c.index = -1
|
c.index = -1
|
||||||
|
|
||||||
c.fullPath = ""
|
c.fullPath = ""
|
||||||
c.Keys = nil
|
c.Keys = nil
|
||||||
c.Errors = c.Errors[0:0]
|
c.Errors = c.Errors[:0]
|
||||||
c.Accepted = nil
|
c.Accepted = nil
|
||||||
c.queryCache = nil
|
c.queryCache = nil
|
||||||
c.formCache = nil
|
c.formCache = nil
|
||||||
*c.params = (*c.params)[0:0]
|
c.sameSite = 0
|
||||||
|
*c.params = (*c.params)[:0]
|
||||||
|
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||||
@ -112,7 +120,7 @@ func (c *Context) Copy() *Context {
|
|||||||
cp.Writer = &cp.writermem
|
cp.Writer = &cp.writermem
|
||||||
cp.index = abortIndex
|
cp.index = abortIndex
|
||||||
cp.handlers = nil
|
cp.handlers = nil
|
||||||
cp.Keys = map[string]interface{}{}
|
cp.Keys = map[string]any{}
|
||||||
for k, v := range c.Keys {
|
for k, v := range c.Keys {
|
||||||
cp.Keys[k] = v
|
cp.Keys[k] = v
|
||||||
}
|
}
|
||||||
@ -191,7 +199,7 @@ func (c *Context) AbortWithStatus(code int) {
|
|||||||
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
|
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
|
||||||
// This method stops the chain, writes the status code and return a JSON body.
|
// This method stops the chain, writes the status code and return a JSON body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
|
func (c *Context) AbortWithStatusJSON(code int, jsonObj any) {
|
||||||
c.Abort()
|
c.Abort()
|
||||||
c.JSON(code, jsonObj)
|
c.JSON(code, jsonObj)
|
||||||
}
|
}
|
||||||
@ -218,7 +226,8 @@ func (c *Context) Error(err error) *Error {
|
|||||||
panic("err is nil")
|
panic("err is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedError, ok := err.(*Error)
|
var parsedError *Error
|
||||||
|
ok := errors.As(err, &parsedError)
|
||||||
if !ok {
|
if !ok {
|
||||||
parsedError = &Error{
|
parsedError = &Error{
|
||||||
Err: err,
|
Err: err,
|
||||||
@ -236,10 +245,10 @@ func (c *Context) Error(err error) *Error {
|
|||||||
|
|
||||||
// Set is used to store a new key/value pair exclusively for this context.
|
// Set is used to store a new key/value pair exclusively for this context.
|
||||||
// It also lazy initializes c.Keys if it was not used previously.
|
// It also lazy initializes c.Keys if it was not used previously.
|
||||||
func (c *Context) Set(key string, value interface{}) {
|
func (c *Context) Set(key string, value any) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
if c.Keys == nil {
|
if c.Keys == nil {
|
||||||
c.Keys = make(map[string]interface{})
|
c.Keys = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Keys[key] = value
|
c.Keys[key] = value
|
||||||
@ -247,8 +256,8 @@ func (c *Context) Set(key string, value interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for the given key, ie: (value, true).
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
// If the value does not exists it returns (nil, false)
|
// If the value does not exist it returns (nil, false)
|
||||||
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
func (c *Context) Get(key string) (value any, exists bool) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
value, exists = c.Keys[key]
|
value, exists = c.Keys[key]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
@ -256,7 +265,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
||||||
func (c *Context) MustGet(key string) interface{} {
|
func (c *Context) MustGet(key string) any {
|
||||||
if value, exists := c.Get(key); exists {
|
if value, exists := c.Get(key); exists {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -344,9 +353,9 @@ func (c *Context) GetStringSlice(key string) (ss []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStringMap returns the value associated with the key as a map of interfaces.
|
// GetStringMap returns the value associated with the key as a map of interfaces.
|
||||||
func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {
|
func (c *Context) GetStringMap(key string) (sm map[string]any) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
if val, ok := c.Get(key); ok && val != nil {
|
||||||
sm, _ = val.(map[string]interface{})
|
sm, _ = val.(map[string]any)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -381,6 +390,15 @@ func (c *Context) Param(key string) string {
|
|||||||
return c.Params.ByName(key)
|
return c.Params.ByName(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddParam adds param to context and
|
||||||
|
// replaces path param key with given value for e2e testing purposes
|
||||||
|
// Example Route: "/user/:id"
|
||||||
|
// AddParam("id", 1)
|
||||||
|
// Result: "/user/1"
|
||||||
|
func (c *Context) AddParam(key, value string) {
|
||||||
|
c.Params = append(c.Params, Param{Key: key, Value: value})
|
||||||
|
}
|
||||||
|
|
||||||
// Query returns the keyed url query value if it exists,
|
// Query returns the keyed url query value if it exists,
|
||||||
// otherwise it returns an empty string `("")`.
|
// otherwise it returns an empty string `("")`.
|
||||||
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||||
@ -389,9 +407,9 @@ func (c *Context) Param(key string) string {
|
|||||||
// c.Query("name") == "Manu"
|
// c.Query("name") == "Manu"
|
||||||
// c.Query("value") == ""
|
// c.Query("value") == ""
|
||||||
// c.Query("wtf") == ""
|
// c.Query("wtf") == ""
|
||||||
func (c *Context) Query(key string) string {
|
func (c *Context) Query(key string) (value string) {
|
||||||
value, _ := c.GetQuery(key)
|
value, _ = c.GetQuery(key)
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultQuery returns the keyed url query value if it exists,
|
// DefaultQuery returns the keyed url query value if it exists,
|
||||||
@ -425,9 +443,9 @@ func (c *Context) GetQuery(key string) (string, bool) {
|
|||||||
|
|
||||||
// QueryArray returns a slice of strings for a given query key.
|
// QueryArray returns a slice of strings for a given query key.
|
||||||
// The length of the slice depends on the number of params with the given key.
|
// The length of the slice depends on the number of params with the given key.
|
||||||
func (c *Context) QueryArray(key string) []string {
|
func (c *Context) QueryArray(key string) (values []string) {
|
||||||
values, _ := c.GetQueryArray(key)
|
values, _ = c.GetQueryArray(key)
|
||||||
return values
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) initQueryCache() {
|
func (c *Context) initQueryCache() {
|
||||||
@ -442,18 +460,16 @@ func (c *Context) initQueryCache() {
|
|||||||
|
|
||||||
// GetQueryArray returns a slice of strings for a given query key, plus
|
// GetQueryArray returns a slice of strings for a given query key, plus
|
||||||
// a boolean value whether at least one value exists for the given key.
|
// a boolean value whether at least one value exists for the given key.
|
||||||
func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
|
||||||
c.initQueryCache()
|
c.initQueryCache()
|
||||||
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
|
values, ok = c.queryCache[key]
|
||||||
return values, true
|
return
|
||||||
}
|
|
||||||
return []string{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryMap returns a map for a given query key.
|
// QueryMap returns a map for a given query key.
|
||||||
func (c *Context) QueryMap(key string) map[string]string {
|
func (c *Context) QueryMap(key string) (dicts map[string]string) {
|
||||||
dicts, _ := c.GetQueryMap(key)
|
dicts, _ = c.GetQueryMap(key)
|
||||||
return dicts
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryMap returns a map for a given query key, plus a boolean value
|
// GetQueryMap returns a map for a given query key, plus a boolean value
|
||||||
@ -465,9 +481,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
|||||||
|
|
||||||
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
// when it exists, otherwise it returns an empty string `("")`.
|
// when it exists, otherwise it returns an empty string `("")`.
|
||||||
func (c *Context) PostForm(key string) string {
|
func (c *Context) PostForm(key string) (value string) {
|
||||||
value, _ := c.GetPostForm(key)
|
value, _ = c.GetPostForm(key)
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
|
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
@ -496,9 +512,9 @@ func (c *Context) GetPostForm(key string) (string, bool) {
|
|||||||
|
|
||||||
// PostFormArray returns a slice of strings for a given form key.
|
// PostFormArray returns a slice of strings for a given form key.
|
||||||
// The length of the slice depends on the number of params with the given key.
|
// The length of the slice depends on the number of params with the given key.
|
||||||
func (c *Context) PostFormArray(key string) []string {
|
func (c *Context) PostFormArray(key string) (values []string) {
|
||||||
values, _ := c.GetPostFormArray(key)
|
values, _ = c.GetPostFormArray(key)
|
||||||
return values
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) initFormCache() {
|
func (c *Context) initFormCache() {
|
||||||
@ -506,7 +522,7 @@ func (c *Context) initFormCache() {
|
|||||||
c.formCache = make(url.Values)
|
c.formCache = make(url.Values)
|
||||||
req := c.Request
|
req := c.Request
|
||||||
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||||
if err != http.ErrNotMultipart {
|
if !errors.Is(err, http.ErrNotMultipart) {
|
||||||
debugPrint("error on parse multipart form array: %v", err)
|
debugPrint("error on parse multipart form array: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,18 +532,16 @@ func (c *Context) initFormCache() {
|
|||||||
|
|
||||||
// GetPostFormArray returns a slice of strings for a given form key, plus
|
// GetPostFormArray returns a slice of strings for a given form key, plus
|
||||||
// a boolean value whether at least one value exists for the given key.
|
// a boolean value whether at least one value exists for the given key.
|
||||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
|
||||||
c.initFormCache()
|
c.initFormCache()
|
||||||
if values := c.formCache[key]; len(values) > 0 {
|
values, ok = c.formCache[key]
|
||||||
return values, true
|
return
|
||||||
}
|
|
||||||
return []string{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostFormMap returns a map for a given form key.
|
// PostFormMap returns a map for a given form key.
|
||||||
func (c *Context) PostFormMap(key string) map[string]string {
|
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
|
||||||
dicts, _ := c.GetPostFormMap(key)
|
dicts, _ = c.GetPostFormMap(key)
|
||||||
return dicts
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
||||||
@ -591,47 +605,51 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind checks the Content-Type to select a binding engine automatically,
|
// Bind checks the Method and Content-Type to select a binding engine automatically,
|
||||||
// Depending the "Content-Type" header different bindings are used:
|
// Depending on the "Content-Type" header different bindings are used, for example:
|
||||||
// "application/json" --> JSON binding
|
// "application/json" --> JSON binding
|
||||||
// "application/xml" --> XML binding
|
// "application/xml" --> XML binding
|
||||||
// otherwise --> returns an error.
|
|
||||||
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||||
// It decodes the json payload into the struct specified as a pointer.
|
// It decodes the json payload into the struct specified as a pointer.
|
||||||
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
||||||
func (c *Context) Bind(obj interface{}) error {
|
func (c *Context) Bind(obj any) error {
|
||||||
b := binding.Default(c.Request.Method, c.ContentType())
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
return c.MustBindWith(obj, b)
|
return c.MustBindWith(obj, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
|
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
|
||||||
func (c *Context) BindJSON(obj interface{}) error {
|
func (c *Context) BindJSON(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.JSON)
|
return c.MustBindWith(obj, binding.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
|
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
|
||||||
func (c *Context) BindXML(obj interface{}) error {
|
func (c *Context) BindXML(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.XML)
|
return c.MustBindWith(obj, binding.XML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
|
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
|
||||||
func (c *Context) BindQuery(obj interface{}) error {
|
func (c *Context) BindQuery(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.Query)
|
return c.MustBindWith(obj, binding.Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
|
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
|
||||||
func (c *Context) BindYAML(obj interface{}) error {
|
func (c *Context) BindYAML(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.YAML)
|
return c.MustBindWith(obj, binding.YAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
|
||||||
|
func (c *Context) BindTOML(obj interface{}) error {
|
||||||
|
return c.MustBindWith(obj, binding.TOML)
|
||||||
|
}
|
||||||
|
|
||||||
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
||||||
func (c *Context) BindHeader(obj interface{}) error {
|
func (c *Context) BindHeader(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.Header)
|
return c.MustBindWith(obj, binding.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindUri binds the passed struct pointer using binding.Uri.
|
// BindUri binds the passed struct pointer using binding.Uri.
|
||||||
// It will abort the request with HTTP 400 if any error occurs.
|
// It will abort the request with HTTP 400 if any error occurs.
|
||||||
func (c *Context) BindUri(obj interface{}) error {
|
func (c *Context) BindUri(obj any) error {
|
||||||
if err := c.ShouldBindUri(obj); err != nil {
|
if err := c.ShouldBindUri(obj); err != nil {
|
||||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
||||||
return err
|
return err
|
||||||
@ -642,7 +660,7 @@ func (c *Context) BindUri(obj interface{}) error {
|
|||||||
// MustBindWith binds the passed struct pointer using the specified binding engine.
|
// MustBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// It will abort the request with HTTP 400 if any error occurs.
|
// It will abort the request with HTTP 400 if any error occurs.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
|
||||||
if err := c.ShouldBindWith(obj, b); err != nil {
|
if err := c.ShouldBindWith(obj, b); err != nil {
|
||||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
||||||
return err
|
return err
|
||||||
@ -650,46 +668,50 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBind checks the Content-Type to select a binding engine automatically,
|
// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
|
||||||
// Depending the "Content-Type" header different bindings are used:
|
// Depending on the "Content-Type" header different bindings are used, for example:
|
||||||
// "application/json" --> JSON binding
|
// "application/json" --> JSON binding
|
||||||
// "application/xml" --> XML binding
|
// "application/xml" --> XML binding
|
||||||
// otherwise --> returns an error
|
|
||||||
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||||
// It decodes the json payload into the struct specified as a pointer.
|
// It decodes the json payload into the struct specified as a pointer.
|
||||||
// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
|
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
|
||||||
func (c *Context) ShouldBind(obj interface{}) error {
|
func (c *Context) ShouldBind(obj any) error {
|
||||||
b := binding.Default(c.Request.Method, c.ContentType())
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
return c.ShouldBindWith(obj, b)
|
return c.ShouldBindWith(obj, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
|
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
|
||||||
func (c *Context) ShouldBindJSON(obj interface{}) error {
|
func (c *Context) ShouldBindJSON(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.JSON)
|
return c.ShouldBindWith(obj, binding.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
|
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
|
||||||
func (c *Context) ShouldBindXML(obj interface{}) error {
|
func (c *Context) ShouldBindXML(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.XML)
|
return c.ShouldBindWith(obj, binding.XML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
||||||
func (c *Context) ShouldBindQuery(obj interface{}) error {
|
func (c *Context) ShouldBindQuery(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.Query)
|
return c.ShouldBindWith(obj, binding.Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
|
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
|
||||||
func (c *Context) ShouldBindYAML(obj interface{}) error {
|
func (c *Context) ShouldBindYAML(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.YAML)
|
return c.ShouldBindWith(obj, binding.YAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
|
||||||
|
func (c *Context) ShouldBindTOML(obj interface{}) error {
|
||||||
|
return c.ShouldBindWith(obj, binding.TOML)
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
||||||
func (c *Context) ShouldBindHeader(obj interface{}) error {
|
func (c *Context) ShouldBindHeader(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.Header)
|
return c.ShouldBindWith(obj, binding.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
||||||
func (c *Context) ShouldBindUri(obj interface{}) error {
|
func (c *Context) ShouldBindUri(obj any) error {
|
||||||
m := make(map[string][]string)
|
m := make(map[string][]string)
|
||||||
for _, v := range c.Params {
|
for _, v := range c.Params {
|
||||||
m[v.Key] = []string{v.Value}
|
m[v.Key] = []string{v.Value}
|
||||||
@ -699,7 +721,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error {
|
|||||||
|
|
||||||
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
|
||||||
return b.Bind(c.Request, obj)
|
return b.Bind(c.Request, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,7 +730,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
|||||||
//
|
//
|
||||||
// NOTE: This method reads the body before binding. So you should use
|
// NOTE: This method reads the body before binding. So you should use
|
||||||
// ShouldBindWith for better performance if you need to call only once.
|
// ShouldBindWith for better performance if you need to call only once.
|
||||||
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
|
func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
|
||||||
var body []byte
|
var body []byte
|
||||||
if cb, ok := c.Get(BodyBytesKey); ok {
|
if cb, ok := c.Get(BodyBytesKey); ok {
|
||||||
if cbb, ok := cb.([]byte); ok {
|
if cbb, ok := cb.([]byte); ok {
|
||||||
@ -725,33 +747,56 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
|
|||||||
return bb.BindBody(body, obj)
|
return bb.BindBody(body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
// ClientIP implements one best effort algorithm to return the real client IP.
|
||||||
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||||
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
|
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||||
|
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
|
||||||
|
// the remote IP (coming from Request.RemoteAddr) is returned.
|
||||||
func (c *Context) ClientIP() string {
|
func (c *Context) ClientIP() string {
|
||||||
if c.engine.ForwardedByClientIP {
|
// Check if we're running on a trusted platform, continue running backwards if error
|
||||||
clientIP := c.requestHeader("X-Forwarded-For")
|
if c.engine.TrustedPlatform != "" {
|
||||||
clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
|
// Developers can define their own header of Trusted Platform or use predefined constants
|
||||||
if clientIP == "" {
|
if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" {
|
||||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
return addr
|
||||||
}
|
|
||||||
if clientIP != "" {
|
|
||||||
return clientIP
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Legacy "AppEngine" flag
|
||||||
if c.engine.AppEngine {
|
if c.engine.AppEngine {
|
||||||
|
log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`)
|
||||||
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
|
// It also checks if the remoteIP is a trusted proxy or not.
|
||||||
|
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
|
||||||
|
// defined by Engine.SetTrustedProxies()
|
||||||
|
remoteIP := net.ParseIP(c.RemoteIP())
|
||||||
|
if remoteIP == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
trusted := c.engine.isTrustedProxy(remoteIP)
|
||||||
|
|
||||||
|
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
|
||||||
|
for _, headerName := range c.engine.RemoteIPHeaders {
|
||||||
|
ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
|
||||||
|
if valid {
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remoteIP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
|
||||||
|
func (c *Context) RemoteIP() string {
|
||||||
|
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||||
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
// ContentType returns the Content-Type header of the request.
|
// ContentType returns the Content-Type header of the request.
|
||||||
func (c *Context) ContentType() string {
|
func (c *Context) ContentType() string {
|
||||||
@ -794,7 +839,7 @@ func (c *Context) Status(code int) {
|
|||||||
c.Writer.WriteHeader(code)
|
c.Writer.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
|
// Header is an intelligent shortcut for c.Writer.Header().Set(key, value).
|
||||||
// It writes a header in the response.
|
// It writes a header in the response.
|
||||||
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
||||||
func (c *Context) Header(key, value string) {
|
func (c *Context) Header(key, value string) {
|
||||||
@ -810,7 +855,7 @@ func (c *Context) GetHeader(key string) string {
|
|||||||
return c.requestHeader(key)
|
return c.requestHeader(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawData return stream data.
|
// GetRawData returns stream data.
|
||||||
func (c *Context) GetRawData() ([]byte, error) {
|
func (c *Context) GetRawData() ([]byte, error) {
|
||||||
return ioutil.ReadAll(c.Request.Body)
|
return ioutil.ReadAll(c.Request.Body)
|
||||||
}
|
}
|
||||||
@ -870,30 +915,30 @@ func (c *Context) Render(code int, r render.Render) {
|
|||||||
// HTML renders the HTTP template specified by its file name.
|
// HTML renders the HTTP template specified by its file name.
|
||||||
// It also updates the HTTP code and sets the Content-Type as "text/html".
|
// It also updates the HTTP code and sets the Content-Type as "text/html".
|
||||||
// See http://golang.org/doc/articles/wiki/
|
// See http://golang.org/doc/articles/wiki/
|
||||||
func (c *Context) HTML(code int, name string, obj interface{}) {
|
func (c *Context) HTML(code int, name string, obj any) {
|
||||||
instance := c.engine.HTMLRender.Instance(name, obj)
|
instance := c.engine.HTMLRender.Instance(name, obj)
|
||||||
c.Render(code, instance)
|
c.Render(code, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
|
// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
// WARNING: we recommend to use this only for development purposes since printing pretty JSON is
|
// WARNING: we recommend using this only for development purposes since printing pretty JSON is
|
||||||
// more CPU and bandwidth consuming. Use Context.JSON() instead.
|
// more CPU and bandwidth consuming. Use Context.JSON() instead.
|
||||||
func (c *Context) IndentedJSON(code int, obj interface{}) {
|
func (c *Context) IndentedJSON(code int, obj any) {
|
||||||
c.Render(code, render.IndentedJSON{Data: obj})
|
c.Render(code, render.IndentedJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecureJSON serializes the given struct as Secure JSON into the response body.
|
// SecureJSON serializes the given struct as Secure JSON into the response body.
|
||||||
// Default prepends "while(1)," to response body if the given struct is array values.
|
// Default prepends "while(1)," to response body if the given struct is array values.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) SecureJSON(code int, obj interface{}) {
|
func (c *Context) SecureJSON(code int, obj any) {
|
||||||
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
|
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONP serializes the given struct as JSON into the response body.
|
// JSONP serializes the given struct as JSON into the response body.
|
||||||
// It adds padding to response body to request data from a server residing in a different domain than the client.
|
// It adds padding to response body to request data from a server residing in a different domain than the client.
|
||||||
// It also sets the Content-Type as "application/javascript".
|
// It also sets the Content-Type as "application/javascript".
|
||||||
func (c *Context) JSONP(code int, obj interface{}) {
|
func (c *Context) JSONP(code int, obj any) {
|
||||||
callback := c.DefaultQuery("callback", "")
|
callback := c.DefaultQuery("callback", "")
|
||||||
if callback == "" {
|
if callback == "" {
|
||||||
c.Render(code, render.JSON{Data: obj})
|
c.Render(code, render.JSON{Data: obj})
|
||||||
@ -904,40 +949,45 @@ func (c *Context) JSONP(code int, obj interface{}) {
|
|||||||
|
|
||||||
// JSON serializes the given struct as JSON into the response body.
|
// JSON serializes the given struct as JSON into the response body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) JSON(code int, obj interface{}) {
|
func (c *Context) JSON(code int, obj any) {
|
||||||
c.Render(code, render.JSON{Data: obj})
|
c.Render(code, render.JSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
|
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) AsciiJSON(code int, obj interface{}) {
|
func (c *Context) AsciiJSON(code int, obj any) {
|
||||||
c.Render(code, render.AsciiJSON{Data: obj})
|
c.Render(code, render.AsciiJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PureJSON serializes the given struct as JSON into the response body.
|
// PureJSON serializes the given struct as JSON into the response body.
|
||||||
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
||||||
func (c *Context) PureJSON(code int, obj interface{}) {
|
func (c *Context) PureJSON(code int, obj any) {
|
||||||
c.Render(code, render.PureJSON{Data: obj})
|
c.Render(code, render.PureJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// XML serializes the given struct as XML into the response body.
|
// XML serializes the given struct as XML into the response body.
|
||||||
// It also sets the Content-Type as "application/xml".
|
// It also sets the Content-Type as "application/xml".
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
func (c *Context) XML(code int, obj any) {
|
||||||
c.Render(code, render.XML{Data: obj})
|
c.Render(code, render.XML{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// YAML serializes the given struct as YAML into the response body.
|
// YAML serializes the given struct as YAML into the response body.
|
||||||
func (c *Context) YAML(code int, obj interface{}) {
|
func (c *Context) YAML(code int, obj any) {
|
||||||
c.Render(code, render.YAML{Data: obj})
|
c.Render(code, render.YAML{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TOML serializes the given struct as TOML into the response body.
|
||||||
|
func (c *Context) TOML(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.TOML{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
|
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
|
||||||
func (c *Context) ProtoBuf(code int, obj interface{}) {
|
func (c *Context) ProtoBuf(code int, obj any) {
|
||||||
c.Render(code, render.ProtoBuf{Data: obj})
|
c.Render(code, render.ProtoBuf{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
// String writes the given string into the response body.
|
// String writes the given string into the response body.
|
||||||
func (c *Context) String(code int, format string, values ...interface{}) {
|
func (c *Context) String(code int, format string, values ...any) {
|
||||||
c.Render(code, render.String{Format: format, Data: values})
|
c.Render(code, render.String{Format: format, Data: values})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -946,7 +996,7 @@ func (c *Context) StringHTML(code int, content string, values ...interface{}) {
|
|||||||
c.Render(code, render.StringHTML{Format: content, Data: values})
|
c.Render(code, render.StringHTML{Format: content, Data: values})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect returns a HTTP redirect to the specific location.
|
// Redirect returns an HTTP redirect to the specific location.
|
||||||
func (c *Context) Redirect(code int, location string) {
|
func (c *Context) Redirect(code int, location string) {
|
||||||
c.Render(-1, render.Redirect{
|
c.Render(-1, render.Redirect{
|
||||||
Code: code,
|
Code: code,
|
||||||
@ -992,12 +1042,16 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
|
|||||||
// FileAttachment writes the specified file into the body stream in an efficient way
|
// FileAttachment writes the specified file into the body stream in an efficient way
|
||||||
// On the client side, the file will typically be downloaded with the given filename
|
// On the client side, the file will typically be downloaded with the given filename
|
||||||
func (c *Context) FileAttachment(filepath, filename string) {
|
func (c *Context) FileAttachment(filepath, filename string) {
|
||||||
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
if isASCII(filename) {
|
||||||
|
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
|
||||||
|
} else {
|
||||||
|
c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
|
||||||
|
}
|
||||||
http.ServeFile(c.Writer, c.Request, filepath)
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSEvent writes a Server-Sent Event into the body stream.
|
// SSEvent writes a Server-Sent Event into the body stream.
|
||||||
func (c *Context) SSEvent(name string, message interface{}) {
|
func (c *Context) SSEvent(name string, message any) {
|
||||||
c.Render(-1, sse.Event{
|
c.Render(-1, sse.Event{
|
||||||
Event: name,
|
Event: name,
|
||||||
Data: message,
|
Data: message,
|
||||||
@ -1031,14 +1085,15 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool {
|
|||||||
type Negotiate struct {
|
type Negotiate struct {
|
||||||
Offered []string
|
Offered []string
|
||||||
HTMLName string
|
HTMLName string
|
||||||
HTMLData interface{}
|
HTMLData any
|
||||||
JSONData interface{}
|
JSONData any
|
||||||
XMLData interface{}
|
XMLData any
|
||||||
YAMLData interface{}
|
YAMLData any
|
||||||
Data interface{}
|
Data any
|
||||||
|
TOMLData any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negotiate calls different Render according acceptable Accept format.
|
// Negotiate calls different Render according to acceptable Accept format.
|
||||||
func (c *Context) Negotiate(code int, config Negotiate) {
|
func (c *Context) Negotiate(code int, config Negotiate) {
|
||||||
switch c.NegotiateFormat(config.Offered...) {
|
switch c.NegotiateFormat(config.Offered...) {
|
||||||
case binding.MIMEJSON:
|
case binding.MIMEJSON:
|
||||||
@ -1057,6 +1112,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||||||
data := chooseData(config.YAMLData, config.Data)
|
data := chooseData(config.YAMLData, config.Data)
|
||||||
c.YAML(code, data)
|
c.YAML(code, data)
|
||||||
|
|
||||||
|
case binding.MIMETOML:
|
||||||
|
data := chooseData(config.TOMLData, config.Data)
|
||||||
|
c.TOML(code, data)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
|
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
|
||||||
}
|
}
|
||||||
@ -1102,34 +1161,47 @@ func (c *Context) SetAccepted(formats ...string) {
|
|||||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
||||||
// Deadline always returns that there is no deadline (ok==false),
|
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
|
||||||
// maybe you want to use Request.Context().Deadline() instead.
|
|
||||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return c.Request.Context().Deadline()
|
||||||
// Done always returns nil (chan which will wait forever),
|
|
||||||
// if you want to abort your work when the connection was closed
|
|
||||||
// you should use Request.Context().Done() instead.
|
|
||||||
func (c *Context) Done() <-chan struct{} {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err always returns nil, maybe you want to use Request.Context().Err() instead.
|
// Done returns nil (chan which will wait forever) when c.Request has no Context.
|
||||||
func (c *Context) Err() error {
|
func (c *Context) Done() <-chan struct{} {
|
||||||
|
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return c.Request.Context().Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns nil when c.Request has no Context.
|
||||||
|
func (c *Context) Err() error {
|
||||||
|
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.Request.Context().Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Value returns the value associated with this context for key, or nil
|
// Value returns the value associated with this context for key, or nil
|
||||||
// if no value is associated with key. Successive calls to Value with
|
// if no value is associated with key. Successive calls to Value with
|
||||||
// the same key returns the same result.
|
// the same key returns the same result.
|
||||||
func (c *Context) Value(key interface{}) interface{} {
|
func (c *Context) Value(key any) any {
|
||||||
if key == 0 {
|
if key == 0 {
|
||||||
return c.Request
|
return c.Request
|
||||||
}
|
}
|
||||||
|
if key == ContextKey {
|
||||||
|
return c
|
||||||
|
}
|
||||||
if keyAsString, ok := key.(string); ok {
|
if keyAsString, ok := key.(string); ok {
|
||||||
val, _ := c.Get(keyAsString)
|
if val, exists := c.Get(keyAsString); exists {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return c.Request.Context().Value(key)
|
||||||
|
}
|
||||||
|
|||||||
31
context_1.16_test.go
Normal file
31
context_1.16_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2021 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.17
|
||||||
|
// +build !go1.17
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextFormFileFailed16(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
mw := multipart.NewWriter(buf)
|
||||||
|
mw.Close()
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
|
c.engine.MaxMultipartMemory = 8 << 20
|
||||||
|
f, err := c.FormFile("file")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, f)
|
||||||
|
}
|
||||||
72
context_1.17_test.go
Normal file
72
context_1.17_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2021 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.17
|
||||||
|
// +build go1.17
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type interceptedWriter struct {
|
||||||
|
ResponseWriter
|
||||||
|
b *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i interceptedWriter) WriteHeader(code int) {
|
||||||
|
i.Header().Del("X-Test")
|
||||||
|
i.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextFormFileFailed17(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
mw := multipart.NewWriter(buf)
|
||||||
|
mw.Close()
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
|
c.engine.MaxMultipartMemory = 8 << 20
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
f, err := c.FormFile("file")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterceptedHeader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, r := CreateTestContext(w)
|
||||||
|
|
||||||
|
r.Use(func(c *Context) {
|
||||||
|
i := interceptedWriter{
|
||||||
|
ResponseWriter: c.Writer,
|
||||||
|
b: bytes.NewBuffer(nil),
|
||||||
|
}
|
||||||
|
c.Writer = i
|
||||||
|
c.Next()
|
||||||
|
c.Header("X-Test", "overridden")
|
||||||
|
c.Writer = i.ResponseWriter
|
||||||
|
})
|
||||||
|
r.GET("/", func(c *Context) {
|
||||||
|
c.Header("X-Test", "original")
|
||||||
|
c.Header("X-Test-2", "present")
|
||||||
|
c.String(http.StatusOK, "hello world")
|
||||||
|
})
|
||||||
|
c.Request = httptest.NewRequest("GET", "/", nil)
|
||||||
|
r.HandleContext(c)
|
||||||
|
// Result() has headers frozen when WriteHeaderNow() has been called
|
||||||
|
// Compared to this time, this is when the response headers will be flushed
|
||||||
|
// As response is flushed on c.String, the Header cannot be set by the first
|
||||||
|
// middleware. Assert this
|
||||||
|
assert.Equal(t, "", w.Result().Header.Get("X-Test"))
|
||||||
|
assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
// +build appengine
|
|
||||||
|
|
||||||
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
defaultAppEngine = true
|
defaultPlatform = PlatformGoogleAppEngine
|
||||||
}
|
}
|
||||||
|
|||||||
470
context_test.go
470
context_test.go
@ -12,8 +12,10 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -23,10 +25,9 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-contrib/sse"
|
"github.com/gin-contrib/sse"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ context.Context = &Context{}
|
var _ context.Context = &Context{}
|
||||||
@ -87,19 +88,6 @@ func TestContextFormFile(t *testing.T) {
|
|||||||
assert.NoError(t, c.SaveUploadedFile(f, "test"))
|
assert.NoError(t, c.SaveUploadedFile(f, "test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextFormFileFailed(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
mw := multipart.NewWriter(buf)
|
|
||||||
mw.Close()
|
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
|
||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
|
||||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
|
||||||
c.engine.MaxMultipartMemory = 8 << 20
|
|
||||||
f, err := c.FormFile("file")
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContextMultipartForm(t *testing.T) {
|
func TestContextMultipartForm(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(buf)
|
mw := multipart.NewWriter(buf)
|
||||||
@ -224,7 +212,7 @@ func TestContextSetGetValues(t *testing.T) {
|
|||||||
c.Set("uint64", uint64(42))
|
c.Set("uint64", uint64(42))
|
||||||
c.Set("float32", float32(4.2))
|
c.Set("float32", float32(4.2))
|
||||||
c.Set("float64", 4.2)
|
c.Set("float64", 4.2)
|
||||||
var a interface{} = 1
|
var a any = 1
|
||||||
c.Set("intInterface", a)
|
c.Set("intInterface", a)
|
||||||
|
|
||||||
assert.Exactly(t, c.MustGet("string").(string), "this is a string")
|
assert.Exactly(t, c.MustGet("string").(string), "this is a string")
|
||||||
@ -234,7 +222,6 @@ func TestContextSetGetValues(t *testing.T) {
|
|||||||
assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2))
|
assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2))
|
||||||
assert.Exactly(t, c.MustGet("float64").(float64), 4.2)
|
assert.Exactly(t, c.MustGet("float64").(float64), 4.2)
|
||||||
assert.Exactly(t, c.MustGet("intInterface").(int), 1)
|
assert.Exactly(t, c.MustGet("intInterface").(int), 1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextGetString(t *testing.T) {
|
func TestContextGetString(t *testing.T) {
|
||||||
@ -300,7 +287,7 @@ func TestContextGetStringSlice(t *testing.T) {
|
|||||||
|
|
||||||
func TestContextGetStringMap(t *testing.T) {
|
func TestContextGetStringMap(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
var m = make(map[string]interface{})
|
m := make(map[string]any)
|
||||||
m["foo"] = 1
|
m["foo"] = 1
|
||||||
c.Set("map", m)
|
c.Set("map", m)
|
||||||
|
|
||||||
@ -310,7 +297,7 @@ func TestContextGetStringMap(t *testing.T) {
|
|||||||
|
|
||||||
func TestContextGetStringMapString(t *testing.T) {
|
func TestContextGetStringMapString(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
var m = make(map[string]string)
|
m := make(map[string]string)
|
||||||
m["foo"] = "bar"
|
m["foo"] = "bar"
|
||||||
c.Set("map", m)
|
c.Set("map", m)
|
||||||
|
|
||||||
@ -320,7 +307,7 @@ func TestContextGetStringMapString(t *testing.T) {
|
|||||||
|
|
||||||
func TestContextGetStringMapStringSlice(t *testing.T) {
|
func TestContextGetStringMapStringSlice(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
var m = make(map[string][]string)
|
m := make(map[string][]string)
|
||||||
m["foo"] = []string{"foo"}
|
m["foo"] = []string{"foo"}
|
||||||
c.Set("map", m)
|
c.Set("map", m)
|
||||||
|
|
||||||
@ -369,15 +356,12 @@ func TestContextHandlerNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handlerNameTest(c *Context) {
|
func handlerNameTest(c *Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerNameTest2(c *Context) {
|
func handlerNameTest2(c *Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var handlerTest HandlerFunc = func(c *Context) {
|
var handlerTest HandlerFunc = func(c *Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextHandler(t *testing.T) {
|
func TestContextHandler(t *testing.T) {
|
||||||
@ -659,8 +643,7 @@ func TestContextBodyAllowedForStatus(t *testing.T) {
|
|||||||
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestPanicRender struct {
|
type TestPanicRender struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (*TestPanicRender) Render(http.ResponseWriter) error {
|
func (*TestPanicRender) Render(http.ResponseWriter) error {
|
||||||
return errors.New("TestPanicRender")
|
return errors.New("TestPanicRender")
|
||||||
@ -1031,7 +1014,9 @@ func TestContextRenderFile(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
|
// else, Content-Type='text/x-go; charset=utf-8'
|
||||||
|
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderFileFromFS(t *testing.T) {
|
func TestContextRenderFileFromFS(t *testing.T) {
|
||||||
@ -1043,7 +1028,9 @@ func TestContextRenderFileFromFS(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
|
// else, Content-Type='text/x-go; charset=utf-8'
|
||||||
|
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
||||||
assert.Equal(t, "/some/path", c.Request.URL.Path)
|
assert.Equal(t, "/some/path", c.Request.URL.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1057,7 +1044,20 @@ func TestContextRenderAttachment(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||||
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition"))
|
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextRenderUTF8Attachment(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
newFilename := "new🧡_filename.go"
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||||
|
c.FileAttachment("./gin.go", newFilename)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||||
|
assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextRenderYAML tests that the response is serialized as YAML
|
// TestContextRenderYAML tests that the response is serialized as YAML
|
||||||
@ -1073,6 +1073,19 @@ func TestContextRenderYAML(t *testing.T) {
|
|||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextRenderTOML tests that the response is serialized as TOML
|
||||||
|
// and Content-Type is set to application/toml
|
||||||
|
func TestContextRenderTOML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.TOML(http.StatusCreated, H{"foo": "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Equal(t, "foo = 'bar'\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
|
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
|
||||||
// and Content-Type is set to application/x-protobuf
|
// and Content-Type is set to application/x-protobuf
|
||||||
// and we just use the example protobuf to check if the response is correct
|
// and we just use the example protobuf to check if the response is correct
|
||||||
@ -1193,6 +1206,36 @@ func TestContextNegotiationWithXML(t *testing.T) {
|
|||||||
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextNegotiationWithYAML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
|
Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML},
|
||||||
|
Data: H{"foo": "bar"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextNegotiationWithTOML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
|
Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML},
|
||||||
|
Data: H{"foo": "bar"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "foo = 'bar'\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextNegotiationWithHTML(t *testing.T) {
|
func TestContextNegotiationWithHTML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, router := CreateTestContext(w)
|
c, router := CreateTestContext(w)
|
||||||
@ -1338,7 +1381,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
|||||||
_, err := buf.ReadFrom(w.Body)
|
_, err := buf.ReadFrom(w.Body)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
jsonStringBody := buf.String()
|
jsonStringBody := buf.String()
|
||||||
assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody)
|
assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextError(t *testing.T) {
|
func TestContextError(t *testing.T) {
|
||||||
@ -1404,12 +1447,11 @@ func TestContextAbortWithError(t *testing.T) {
|
|||||||
func TestContextClientIP(t *testing.T) {
|
func TestContextClientIP(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()
|
||||||
|
resetContextForClientIPTests(c)
|
||||||
|
|
||||||
c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ")
|
// Legacy tests (validating that the defaults don't break the
|
||||||
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
|
// (insecure!) old behaviour)
|
||||||
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
|
|
||||||
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
|
||||||
|
|
||||||
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||||
|
|
||||||
c.Request.Header.Del("X-Forwarded-For")
|
c.Request.Header.Del("X-Forwarded-For")
|
||||||
@ -1420,7 +1462,7 @@ func TestContextClientIP(t *testing.T) {
|
|||||||
|
|
||||||
c.Request.Header.Del("X-Forwarded-For")
|
c.Request.Header.Del("X-Forwarded-For")
|
||||||
c.Request.Header.Del("X-Real-IP")
|
c.Request.Header.Del("X-Real-IP")
|
||||||
c.engine.AppEngine = true
|
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||||
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||||
|
|
||||||
c.Request.Header.Del("X-Appengine-Remote-Addr")
|
c.Request.Header.Del("X-Appengine-Remote-Addr")
|
||||||
@ -1429,6 +1471,113 @@ func TestContextClientIP(t *testing.T) {
|
|||||||
// no port
|
// no port
|
||||||
c.Request.RemoteAddr = "50.50.50.50"
|
c.Request.RemoteAddr = "50.50.50.50"
|
||||||
assert.Empty(t, c.ClientIP())
|
assert.Empty(t, c.ClientIP())
|
||||||
|
|
||||||
|
// Tests exercising the TrustedProxies functionality
|
||||||
|
resetContextForClientIPTests(c)
|
||||||
|
|
||||||
|
// IPv6 support
|
||||||
|
c.Request.RemoteAddr = "[::1]:12345"
|
||||||
|
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||||
|
|
||||||
|
resetContextForClientIPTests(c)
|
||||||
|
// No trusted proxies
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{})
|
||||||
|
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Disabled TrustedProxies feature
|
||||||
|
_ = c.engine.SetTrustedProxies(nil)
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Last proxy is trusted, but the RemoteAddr is not
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"30.30.30.30"})
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Only trust RemoteAddr
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||||
|
assert.Equal(t, "30.30.30.30", c.ClientIP())
|
||||||
|
|
||||||
|
// All steps are trusted
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"})
|
||||||
|
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||||
|
|
||||||
|
// Use CIDR
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"40.40.25.25/16", "30.30.30.30"})
|
||||||
|
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||||
|
|
||||||
|
// Use hostname that resolves to all the proxies
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"foo"})
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Use hostname that returns an error
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"bar"})
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// X-Forwarded-For has a non-IP element
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||||
|
c.Request.Header.Set("X-Forwarded-For", " blah ")
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Result from LookupHost has non-IP element. This should never
|
||||||
|
// happen, but we should test it to make sure we handle it
|
||||||
|
// gracefully.
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"baz"})
|
||||||
|
c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ")
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||||
|
c.Request.Header.Del("X-Forwarded-For")
|
||||||
|
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"}
|
||||||
|
assert.Equal(t, "10.10.10.10", c.ClientIP())
|
||||||
|
|
||||||
|
c.engine.RemoteIPHeaders = []string{}
|
||||||
|
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||||
|
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||||
|
|
||||||
|
// Use custom TrustedPlatform header
|
||||||
|
c.engine.TrustedPlatform = "X-CDN-IP"
|
||||||
|
c.Request.Header.Set("X-CDN-IP", "80.80.80.80")
|
||||||
|
assert.Equal(t, "80.80.80.80", c.ClientIP())
|
||||||
|
// wrong header
|
||||||
|
c.engine.TrustedPlatform = "X-Wrong-Header"
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
c.Request.Header.Del("X-CDN-IP")
|
||||||
|
// TrustedPlatform is empty
|
||||||
|
c.engine.TrustedPlatform = ""
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
// Test the legacy flag
|
||||||
|
c.engine.AppEngine = true
|
||||||
|
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||||
|
c.engine.AppEngine = false
|
||||||
|
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||||
|
|
||||||
|
c.Request.Header.Del("X-Appengine-Remote-Addr")
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
c.engine.TrustedPlatform = PlatformCloudflare
|
||||||
|
assert.Equal(t, "60.60.60.60", c.ClientIP())
|
||||||
|
|
||||||
|
c.Request.Header.Del("CF-Connecting-IP")
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
c.engine.TrustedPlatform = ""
|
||||||
|
|
||||||
|
// no port
|
||||||
|
c.Request.RemoteAddr = "50.50.50.50"
|
||||||
|
assert.Empty(t, c.ClientIP())
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetContextForClientIPTests(c *Context) {
|
||||||
|
c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ")
|
||||||
|
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
|
||||||
|
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
|
||||||
|
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
||||||
|
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
||||||
|
c.engine.TrustedPlatform = ""
|
||||||
|
c.engine.trustedCIDRs = defaultTrustedCIDRs
|
||||||
|
c.engine.AppEngine = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextContentType(t *testing.T) {
|
func TestContextContentType(t *testing.T) {
|
||||||
@ -1470,6 +1619,7 @@ func TestContextBindWithJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBindWithXML(t *testing.T) {
|
func TestContextBindWithXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1546,6 +1696,23 @@ func TestContextBindWithYAML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextBindWithTOML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `toml:"foo"`
|
||||||
|
Bar string `toml:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.BindTOML(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBadAutoBind(t *testing.T) {
|
func TestContextBadAutoBind(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1679,6 +1846,23 @@ func TestContextShouldBindWithYAML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindWithTOML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `toml:"foo"`
|
||||||
|
Bar string `toml:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.ShouldBindTOML(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBadAutoShouldBind(t *testing.T) {
|
func TestContextBadAutoShouldBind(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1786,6 +1970,7 @@ func TestContextGolangContext(t *testing.T) {
|
|||||||
assert.Equal(t, ti, time.Time{})
|
assert.Equal(t, ti, time.Time{})
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Equal(t, c.Value(0), c.Request)
|
assert.Equal(t, c.Value(0), c.Request)
|
||||||
|
assert.Equal(t, c.Value(ContextKey), c)
|
||||||
assert.Nil(t, c.Value("foo"))
|
assert.Nil(t, c.Value("foo"))
|
||||||
|
|
||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
@ -1956,8 +2141,8 @@ func TestRaceParamsContextCopy(t *testing.T) {
|
|||||||
}(c.Copy(), c.Param("name"))
|
}(c.Copy(), c.Param("name"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
performRequest(router, "GET", "/name1/api")
|
PerformRequest(router, "GET", "/name1/api")
|
||||||
performRequest(router, "GET", "/name2/api")
|
PerformRequest(router, "GET", "/name2/api")
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1973,3 +2158,212 @@ func TestContextWithKeysMutex(t *testing.T) {
|
|||||||
assert.Nil(t, value)
|
assert.Nil(t, value)
|
||||||
assert.False(t, err)
|
assert.False(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoteIPFail(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request.RemoteAddr = "[:::]:80"
|
||||||
|
ip := net.ParseIP(c.RemoteIP())
|
||||||
|
trust := c.engine.isTrustedProxy(ip)
|
||||||
|
assert.Nil(t, ip)
|
||||||
|
assert.False(t, trust)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
deadline, ok := c.Deadline()
|
||||||
|
assert.Zero(t, deadline)
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
c2, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c2.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
d := time.Now().Add(time.Second)
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||||
|
defer cancel()
|
||||||
|
c2.Request = c2.Request.WithContext(ctx)
|
||||||
|
deadline, ok = c2.Deadline()
|
||||||
|
assert.Equal(t, d, deadline)
|
||||||
|
assert.True(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
assert.Nil(t, c.Done())
|
||||||
|
|
||||||
|
c2, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c2.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
c2.Request = c2.Request.WithContext(ctx)
|
||||||
|
cancel()
|
||||||
|
assert.NotNil(t, <-c2.Done())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
assert.Nil(t, c.Err())
|
||||||
|
|
||||||
|
c2, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c2.engine.ContextWithFallback = true
|
||||||
|
|
||||||
|
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
c2.Request = c2.Request.WithContext(ctx)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
assert.EqualError(t, c2.Err(), context.Canceled.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
getContextAndKey func() (*Context, any)
|
||||||
|
value any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "c with struct context key",
|
||||||
|
getContextAndKey: func() (*Context, any) {
|
||||||
|
var key struct{}
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value"))
|
||||||
|
return c, key
|
||||||
|
},
|
||||||
|
value: "value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "c with string context key",
|
||||||
|
getContextAndKey: func() (*Context, any) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
|
||||||
|
return c, contextKey("key")
|
||||||
|
},
|
||||||
|
value: "value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "c with nil http.Request",
|
||||||
|
getContextAndKey: func() (*Context, any) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
c.Request = nil
|
||||||
|
return c, "key"
|
||||||
|
},
|
||||||
|
value: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "c with nil http.Request.Context()",
|
||||||
|
getContextAndKey: func() (*Context, any) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
// enable ContextWithFallback feature flag
|
||||||
|
c.engine.ContextWithFallback = true
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
return c, "key"
|
||||||
|
},
|
||||||
|
value: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, key := tt.getContextAndKey()
|
||||||
|
assert.Equal(t, tt.value, c.Value(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextCopyShouldNotCancel(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ensureRequestIsOver := make(chan struct{})
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
r.GET("/", func(ginctx *Context) {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
ginctx = ginctx.Copy()
|
||||||
|
|
||||||
|
// start async goroutine for calling srv
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
<-ensureRequestIsOver // ensure request is done
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ginctx, http.MethodGet, srv.URL, nil)
|
||||||
|
must(err)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fmt.Errorf("request error: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Error(fmt.Errorf("unexpected status code: %s", res.Status))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", ":0")
|
||||||
|
must(err)
|
||||||
|
go func() {
|
||||||
|
s := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
must(s.Serve(l))
|
||||||
|
}()
|
||||||
|
|
||||||
|
addr := strings.Split(l.Addr().String(), ":")
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/", addr[len(addr)-1]))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fmt.Errorf("request error: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ensureRequestIsOver)
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Error(fmt.Errorf("unexpected status code: %s", res.Status))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextAddParam(t *testing.T) {
|
||||||
|
c := &Context{}
|
||||||
|
id := "id"
|
||||||
|
value := "1"
|
||||||
|
c.AddParam(id, value)
|
||||||
|
|
||||||
|
v, ok := c.Params.Get(id)
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, value, v)
|
||||||
|
}
|
||||||
|
|||||||
12
debug.go
12
debug.go
@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 10
|
const ginSupportMinGoVer = 15
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
@ -47,7 +47,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugPrint(format string, values ...interface{}) {
|
func debugPrint(format string, values ...any) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
if !strings.HasSuffix(format, "\n") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
@ -66,8 +66,8 @@ func getMinVer(v string) (uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.
|
debugPrint(`[WARNING] Now Gin requires Go 1.15+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
@ -95,9 +95,7 @@ at initialization. ie. before any route is registered or the router is listening
|
|||||||
}
|
}
|
||||||
|
|
||||||
func debugPrintError(err error) {
|
func debugPrintError(err error) {
|
||||||
if err != nil {
|
if err != nil && IsDebugging() {
|
||||||
if IsDebugging() {
|
|
||||||
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -103,8 +103,8 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
|||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m <= ginSupportMinGoVer {
|
if e == nil && m < ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// BindWith binds the passed struct pointer using the specified binding engine.
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
||||||
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
||||||
be deprecated, please check issue #662 and either use MustBindWith() if you
|
be deprecated, please check issue #662 and either use MustBindWith() if you
|
||||||
want HTTP 400 to be automatically returned if any error occur, or use
|
want HTTP 400 to be automatically returned if any error occur, or use
|
||||||
|
|||||||
12
errors.go
12
errors.go
@ -34,7 +34,7 @@ const (
|
|||||||
type Error struct {
|
type Error struct {
|
||||||
Err error
|
Err error
|
||||||
Type ErrorType
|
Type ErrorType
|
||||||
Meta interface{}
|
Meta any
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorMsgs []*Error
|
type errorMsgs []*Error
|
||||||
@ -48,13 +48,13 @@ func (msg *Error) SetType(flags ErrorType) *Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetMeta sets the error's meta data.
|
// SetMeta sets the error's meta data.
|
||||||
func (msg *Error) SetMeta(data interface{}) *Error {
|
func (msg *Error) SetMeta(data any) *Error {
|
||||||
msg.Meta = data
|
msg.Meta = data
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON creates a properly formatted JSON
|
// JSON creates a properly formatted JSON
|
||||||
func (msg *Error) JSON() interface{} {
|
func (msg *Error) JSON() any {
|
||||||
jsonData := H{}
|
jsonData := H{}
|
||||||
if msg.Meta != nil {
|
if msg.Meta != nil {
|
||||||
value := reflect.ValueOf(msg.Meta)
|
value := reflect.ValueOf(msg.Meta)
|
||||||
@ -122,7 +122,7 @@ func (a errorMsgs) Last() *Error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors returns an array will all the error messages.
|
// Errors returns an array with all the error messages.
|
||||||
// Example:
|
// Example:
|
||||||
// c.Error(errors.New("first"))
|
// c.Error(errors.New("first"))
|
||||||
// c.Error(errors.New("second"))
|
// c.Error(errors.New("second"))
|
||||||
@ -139,14 +139,14 @@ func (a errorMsgs) Errors() []string {
|
|||||||
return errorStrings
|
return errorStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a errorMsgs) JSON() interface{} {
|
func (a errorMsgs) JSON() any {
|
||||||
switch length := len(a); length {
|
switch length := len(a); length {
|
||||||
case 0:
|
case 0:
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
case 1:
|
||||||
return a.Last().JSON()
|
return a.Last().JSON()
|
||||||
default:
|
default:
|
||||||
jsonData := make([]interface{}, length)
|
jsonData := make([]any, length)
|
||||||
for i, err := range a {
|
for i, err := range a {
|
||||||
jsonData[i] = err.JSON()
|
jsonData[i] = err.JSON()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
// +build go1.13
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestErr string
|
|
||||||
|
|
||||||
func (e TestErr) Error() string { return string(e) }
|
|
||||||
|
|
||||||
// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
|
|
||||||
// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13,
|
|
||||||
// hence the "// +build go1.13" directive at the beginning of this file.
|
|
||||||
func TestErrorUnwrap(t *testing.T) {
|
|
||||||
innerErr := TestErr("somme error")
|
|
||||||
|
|
||||||
// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
|
|
||||||
err := fmt.Errorf("wrapped: %w", &Error{
|
|
||||||
Err: innerErr,
|
|
||||||
Type: ErrorTypeAny,
|
|
||||||
})
|
|
||||||
|
|
||||||
// check that 'errors.Is()' and 'errors.As()' behave as expected :
|
|
||||||
assert.True(t, errors.Is(err, innerErr))
|
|
||||||
var testErr TestErr
|
|
||||||
assert.True(t, errors.As(err, &testErr))
|
|
||||||
}
|
|
||||||
@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
@ -85,7 +86,7 @@ Error #02: second
|
|||||||
Error #03: third
|
Error #03: third
|
||||||
Meta: map[status:400]
|
Meta: map[status:400]
|
||||||
`, errs.String())
|
`, errs.String())
|
||||||
assert.Equal(t, []interface{}{
|
assert.Equal(t, []any{
|
||||||
H{"error": "first"},
|
H{"error": "first"},
|
||||||
H{"error": "second", "meta": "some data"},
|
H{"error": "second", "meta": "some data"},
|
||||||
H{"error": "third", "status": "400"},
|
H{"error": "third", "status": "400"},
|
||||||
@ -104,3 +105,24 @@ Error #03: third
|
|||||||
assert.Nil(t, errs.JSON())
|
assert.Nil(t, errs.JSON())
|
||||||
assert.Empty(t, errs.String())
|
assert.Empty(t, errs.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestErr string
|
||||||
|
|
||||||
|
func (e TestErr) Error() string { return string(e) }
|
||||||
|
|
||||||
|
// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
|
||||||
|
// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13.
|
||||||
|
func TestErrorUnwrap(t *testing.T) {
|
||||||
|
innerErr := TestErr("some error")
|
||||||
|
|
||||||
|
// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
|
||||||
|
err := fmt.Errorf("wrapped: %w", &Error{
|
||||||
|
Err: innerErr,
|
||||||
|
Type: ErrorTypeAny,
|
||||||
|
})
|
||||||
|
|
||||||
|
// check that 'errors.Is()' and 'errors.As()' behave as expected :
|
||||||
|
assert.True(t, errors.Is(err, innerErr))
|
||||||
|
var testErr TestErr
|
||||||
|
assert.True(t, errors.As(err, &testErr))
|
||||||
|
}
|
||||||
|
|||||||
2
fs.go
2
fs.go
@ -17,7 +17,7 @@ type neuteredReaddirFile struct {
|
|||||||
http.File
|
http.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
|
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
|
||||||
// in router.Static().
|
// in router.Static().
|
||||||
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
||||||
// a filesystem that prevents http.FileServer() to list the directory files.
|
// a filesystem that prevents http.FileServer() to list the directory files.
|
||||||
|
|||||||
264
gin.go
264
gin.go
@ -11,10 +11,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMultipartMemory = 32 << 20 // 32 MB
|
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||||
@ -24,15 +27,26 @@ var (
|
|||||||
default405Body = []byte("405 method not allowed")
|
default405Body = []byte("405 method not allowed")
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAppEngine bool
|
var defaultPlatform string
|
||||||
|
|
||||||
|
var defaultTrustedCIDRs = []*net.IPNet{
|
||||||
|
{ // 0.0.0.0/0 (IPv4)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
{ // ::/0 (IPv6)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
|
|
||||||
// HandlersChain defines a HandlerFunc array.
|
// HandlersChain defines a HandlerFunc slice.
|
||||||
type HandlersChain []HandlerFunc
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
// Last returns the last handler in the chain. ie. the last handler is the main one.
|
// Last returns the last handler in the chain. i.e. the last handler is the main one.
|
||||||
func (c HandlersChain) Last() HandlerFunc {
|
func (c HandlersChain) Last() HandlerFunc {
|
||||||
if length := len(c); length > 0 {
|
if length := len(c); length > 0 {
|
||||||
return c[length-1]
|
return c[length-1]
|
||||||
@ -48,22 +62,32 @@ type RouteInfo struct {
|
|||||||
HandlerFunc HandlerFunc
|
HandlerFunc HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoutesInfo defines a RouteInfo array.
|
// RoutesInfo defines a RouteInfo slice.
|
||||||
type RoutesInfo []RouteInfo
|
type RoutesInfo []RouteInfo
|
||||||
|
|
||||||
|
// Trusted platforms
|
||||||
|
const (
|
||||||
|
// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
|
||||||
|
// for determining the client's IP
|
||||||
|
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
|
||||||
|
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||||
|
// the client's IP
|
||||||
|
PlatformCloudflare = "CF-Connecting-IP"
|
||||||
|
)
|
||||||
|
|
||||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
// Create an instance of Engine, by using New() or Default()
|
// Create an instance of Engine, by using New() or Default()
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
RouterGroup
|
RouterGroup
|
||||||
|
|
||||||
// Enables automatic redirection if the current route can't be matched but a
|
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
|
||||||
// handler for the path with (without) the trailing slash exists.
|
// handler for the path with (without) the trailing slash exists.
|
||||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
// client is redirected to /foo with http status code 301 for GET requests
|
// client is redirected to /foo with http status code 301 for GET requests
|
||||||
// and 307 for all other request methods.
|
// and 307 for all other request methods.
|
||||||
RedirectTrailingSlash bool
|
RedirectTrailingSlash bool
|
||||||
|
|
||||||
// If enabled, the router tries to fix the current request path, if no
|
// RedirectFixedPath if enabled, the router tries to fix the current request path, if no
|
||||||
// handle is registered for it.
|
// handle is registered for it.
|
||||||
// First superfluous path elements like ../ or // are removed.
|
// First superfluous path elements like ../ or // are removed.
|
||||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||||
@ -74,35 +98,58 @@ type Engine struct {
|
|||||||
// RedirectTrailingSlash is independent of this option.
|
// RedirectTrailingSlash is independent of this option.
|
||||||
RedirectFixedPath bool
|
RedirectFixedPath bool
|
||||||
|
|
||||||
// If enabled, the router checks if another method is allowed for the
|
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
|
||||||
// current route, if the current request can not be routed.
|
// current route, if the current request can not be routed.
|
||||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||||
// and HTTP status code 405.
|
// and HTTP status code 405.
|
||||||
// If no other Method is allowed, the request is delegated to the NotFound
|
// If no other Method is allowed, the request is delegated to the NotFound
|
||||||
// handler.
|
// handler.
|
||||||
HandleMethodNotAllowed bool
|
HandleMethodNotAllowed bool
|
||||||
|
|
||||||
|
// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
|
||||||
|
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
|
||||||
|
// fetched, it falls back to the IP obtained from
|
||||||
|
// `(*gin.Context).Request.RemoteAddr`.
|
||||||
ForwardedByClientIP bool
|
ForwardedByClientIP bool
|
||||||
|
|
||||||
// #726 #755 If enabled, it will thrust some headers starting with
|
// AppEngine was deprecated.
|
||||||
|
// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
|
||||||
|
// #726 #755 If enabled, it will trust some headers starting with
|
||||||
// 'X-AppEngine...' for better integration with that PaaS.
|
// 'X-AppEngine...' for better integration with that PaaS.
|
||||||
AppEngine bool
|
AppEngine bool
|
||||||
|
|
||||||
// If enabled, the url.RawPath will be used to find parameters.
|
// UseRawPath if enabled, the url.RawPath will be used to find parameters.
|
||||||
UseRawPath bool
|
UseRawPath bool
|
||||||
|
|
||||||
// If true, the path value will be unescaped.
|
// UnescapePathValues if true, the path value will be unescaped.
|
||||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||||
// as url.Path gonna be used, which is already unescaped.
|
// as url.Path gonna be used, which is already unescaped.
|
||||||
UnescapePathValues bool
|
UnescapePathValues bool
|
||||||
|
|
||||||
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
|
||||||
// method call.
|
|
||||||
MaxMultipartMemory int64
|
|
||||||
|
|
||||||
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
||||||
// See the PR #1817 and issue #1644
|
// See the PR #1817 and issue #1644
|
||||||
RemoveExtraSlash bool
|
RemoveExtraSlash bool
|
||||||
|
|
||||||
|
// RemoteIPHeaders list of headers used to obtain the client IP when
|
||||||
|
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||||
|
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||||
|
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
|
||||||
|
RemoteIPHeaders []string
|
||||||
|
|
||||||
|
// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
|
||||||
|
// that platform, for example to determine the client IP
|
||||||
|
TrustedPlatform string
|
||||||
|
|
||||||
|
// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||||
|
// method call.
|
||||||
|
MaxMultipartMemory int64
|
||||||
|
|
||||||
|
// UseH2C enable h2c support.
|
||||||
|
UseH2C bool
|
||||||
|
|
||||||
|
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
|
||||||
|
ContextWithFallback bool
|
||||||
|
|
||||||
delims render.Delims
|
delims render.Delims
|
||||||
secureJSONPrefix string
|
secureJSONPrefix string
|
||||||
HTMLRender render.HTMLRender
|
HTMLRender render.HTMLRender
|
||||||
@ -114,12 +161,15 @@ type Engine struct {
|
|||||||
pool sync.Pool
|
pool sync.Pool
|
||||||
trees methodTrees
|
trees methodTrees
|
||||||
maxParams uint16
|
maxParams uint16
|
||||||
|
maxSections uint16
|
||||||
|
trustedProxies []string
|
||||||
|
trustedCIDRs []*net.IPNet
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IRouter = &Engine{}
|
var _ IRouter = &Engine{}
|
||||||
|
|
||||||
// New returns a new blank Engine instance without any middleware attached.
|
// New returns a new blank Engine instance without any middleware attached.
|
||||||
// By default the configuration is:
|
// By default, the configuration is:
|
||||||
// - RedirectTrailingSlash: true
|
// - RedirectTrailingSlash: true
|
||||||
// - RedirectFixedPath: false
|
// - RedirectFixedPath: false
|
||||||
// - HandleMethodNotAllowed: false
|
// - HandleMethodNotAllowed: false
|
||||||
@ -139,7 +189,8 @@ func New() *Engine {
|
|||||||
RedirectFixedPath: false,
|
RedirectFixedPath: false,
|
||||||
HandleMethodNotAllowed: false,
|
HandleMethodNotAllowed: false,
|
||||||
ForwardedByClientIP: true,
|
ForwardedByClientIP: true,
|
||||||
AppEngine: defaultAppEngine,
|
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||||
|
TrustedPlatform: defaultPlatform,
|
||||||
UseRawPath: false,
|
UseRawPath: false,
|
||||||
RemoveExtraSlash: false,
|
RemoveExtraSlash: false,
|
||||||
UnescapePathValues: true,
|
UnescapePathValues: true,
|
||||||
@ -147,9 +198,11 @@ func New() *Engine {
|
|||||||
trees: make(methodTrees, 0, 9),
|
trees: make(methodTrees, 0, 9),
|
||||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||||
secureJSONPrefix: "while(1);",
|
secureJSONPrefix: "while(1);",
|
||||||
|
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||||
|
trustedCIDRs: defaultTrustedCIDRs,
|
||||||
}
|
}
|
||||||
engine.RouterGroup.engine = engine
|
engine.RouterGroup.engine = engine
|
||||||
engine.pool.New = func() interface{} {
|
engine.pool.New = func() any {
|
||||||
return engine.allocateContext()
|
return engine.allocateContext()
|
||||||
}
|
}
|
||||||
return engine
|
return engine
|
||||||
@ -163,12 +216,22 @@ func Default() *Engine {
|
|||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) allocateContext() *Context {
|
func (engine *Engine) Handler() http.Handler {
|
||||||
v := make(Params, 0, engine.maxParams)
|
if !engine.UseH2C {
|
||||||
return &Context{engine: engine, params: &v}
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delims sets template left and right delims and returns a Engine instance.
|
h2s := &http2.Server{}
|
||||||
|
return h2c.NewHandler(engine, h2s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) allocateContext() *Context {
|
||||||
|
v := make(Params, 0, engine.maxParams)
|
||||||
|
skippedNodes := make([]skippedNode, 0, engine.maxSections)
|
||||||
|
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets template left and right delims and returns an Engine instance.
|
||||||
func (engine *Engine) Delims(left, right string) *Engine {
|
func (engine *Engine) Delims(left, right string) *Engine {
|
||||||
engine.delims = render.Delims{Left: left, Right: right}
|
engine.delims = render.Delims{Left: left, Right: right}
|
||||||
return engine
|
return engine
|
||||||
@ -222,19 +285,19 @@ func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
|
|||||||
engine.FuncMap = funcMap
|
engine.FuncMap = funcMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
|
||||||
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||||
engine.noRoute = handlers
|
engine.noRoute = handlers
|
||||||
engine.rebuild404Handlers()
|
engine.rebuild404Handlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoMethod sets the handlers called when... TODO.
|
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
|
||||||
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||||
engine.noMethod = handlers
|
engine.noMethod = handlers
|
||||||
engine.rebuild405Handlers()
|
engine.rebuild405Handlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
|
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
|
||||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
// For example, this is the right place for a logger or error management middleware.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
@ -271,6 +334,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
|||||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||||
engine.maxParams = paramsCount
|
engine.maxParams = paramsCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
|
||||||
|
engine.maxSections = sectionsCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||||
@ -305,12 +372,120 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
|||||||
func (engine *Engine) Run(addr ...string) (err error) {
|
func (engine *Engine) Run(addr ...string) (err error) {
|
||||||
defer func() { debugPrintError(err) }()
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
address := resolveAddress(addr)
|
address := resolveAddress(addr)
|
||||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||||
err = http.ListenAndServe(address, engine)
|
err = http.ListenAndServe(address, engine.Handler())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||||
|
if engine.trustedProxies == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
|
||||||
|
for _, trustedProxy := range engine.trustedProxies {
|
||||||
|
if !strings.Contains(trustedProxy, "/") {
|
||||||
|
ip := parseIP(trustedProxy)
|
||||||
|
if ip == nil {
|
||||||
|
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(ip) {
|
||||||
|
case net.IPv4len:
|
||||||
|
trustedProxy += "/32"
|
||||||
|
case net.IPv6len:
|
||||||
|
trustedProxy += "/128"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, cidrNet, err := net.ParseCIDR(trustedProxy)
|
||||||
|
if err != nil {
|
||||||
|
return cidr, err
|
||||||
|
}
|
||||||
|
cidr = append(cidr, cidrNet)
|
||||||
|
}
|
||||||
|
return cidr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrustedProxies set a list of network origins (IPv4 addresses,
|
||||||
|
// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
|
||||||
|
// request's headers that contain alternative client IP when
|
||||||
|
// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
|
||||||
|
// feature is enabled by default, and it also trusts all proxies
|
||||||
|
// by default. If you want to disable this feature, use
|
||||||
|
// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
|
||||||
|
// return the remote address directly.
|
||||||
|
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
|
||||||
|
engine.trustedProxies = trustedProxies
|
||||||
|
return engine.parseTrustedProxies()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
|
||||||
|
func (engine *Engine) isUnsafeTrustedProxies() bool {
|
||||||
|
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
|
||||||
|
func (engine *Engine) parseTrustedProxies() error {
|
||||||
|
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||||
|
engine.trustedCIDRs = trustedCIDRs
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
|
||||||
|
func (engine *Engine) isTrustedProxy(ip net.IP) bool {
|
||||||
|
if engine.trustedCIDRs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, cidr := range engine.trustedCIDRs {
|
||||||
|
if cidr.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
|
||||||
|
func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
|
||||||
|
if header == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
items := strings.Split(header, ",")
|
||||||
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
|
ipStr := strings.TrimSpace(items[i])
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-Forwarded-For is appended by proxy
|
||||||
|
// Check IPs in reverse order and stop when find untrusted proxy
|
||||||
|
if (i == 0) || (!engine.isTrustedProxy(ip)) {
|
||||||
|
return ipStr, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIP parse a string representation of an IP and returns a net.IP with the
|
||||||
|
// minimum byte representation or nil if input is invalid.
|
||||||
|
func parseIP(ip string) net.IP {
|
||||||
|
parsedIP := net.ParseIP(ip)
|
||||||
|
|
||||||
|
if ipv4 := parsedIP.To4(); ipv4 != nil {
|
||||||
|
// return ip in a 4-byte representation
|
||||||
|
return ipv4
|
||||||
|
}
|
||||||
|
|
||||||
|
// return ip in a 16-byte representation or nil
|
||||||
|
return parsedIP
|
||||||
|
}
|
||||||
|
|
||||||
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
@ -318,17 +493,27 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
|||||||
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||||
defer func() { debugPrintError(err) }()
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
// through the specified unix socket (ie. a file).
|
// through the specified unix socket (i.e. a file).
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func (engine *Engine) RunUnix(file string) (err error) {
|
func (engine *Engine) RunUnix(file string) (err error) {
|
||||||
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||||
defer func() { debugPrintError(err) }()
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("unix", file)
|
listener, err := net.Listen("unix", file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -336,7 +521,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
err = http.Serve(listener, engine)
|
err = http.Serve(listener, engine.Handler())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +532,11 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
|||||||
debugPrint("Listening and serving HTTP on fd@%d", fd)
|
debugPrint("Listening and serving HTTP on fd@%d", fd)
|
||||||
defer func() { debugPrintError(err) }()
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||||
listener, err := net.FileListener(f)
|
listener, err := net.FileListener(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -362,7 +552,13 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
|||||||
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||||
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
||||||
defer func() { debugPrintError(err) }()
|
defer func() { debugPrintError(err) }()
|
||||||
err = http.Serve(listener, engine)
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http.Serve(listener, engine.Handler())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,9 +574,9 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
engine.pool.Put(c)
|
engine.pool.Put(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleContext re-enter a context that has been rewritten.
|
// HandleContext re-enters a context that has been rewritten.
|
||||||
// This can be done by setting c.Request.URL.Path to your new target.
|
// This can be done by setting c.Request.URL.Path to your new target.
|
||||||
// Disclaimer: You can loop yourself to death with this, use wisely.
|
// Disclaimer: You can loop yourself to deal with this, use wisely.
|
||||||
func (engine *Engine) HandleContext(c *Context) {
|
func (engine *Engine) HandleContext(c *Context) {
|
||||||
oldIndexValue := c.index
|
oldIndexValue := c.index
|
||||||
c.reset()
|
c.reset()
|
||||||
@ -410,7 +606,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
}
|
}
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// Find route in tree
|
||||||
value := root.getValue(rPath, c.params, unescape)
|
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
|
||||||
if value.params != nil {
|
if value.params != nil {
|
||||||
c.Params = *value.params
|
c.Params = *value.params
|
||||||
}
|
}
|
||||||
@ -421,7 +617,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && rPath != "/" {
|
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||||
if value.tsr && engine.RedirectTrailingSlash {
|
if value.tsr && engine.RedirectTrailingSlash {
|
||||||
redirectTrailingSlash(c)
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
@ -438,7 +634,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
|
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -37,7 +37,7 @@ func SetHTMLTemplate(templ *template.Template) {
|
|||||||
engine().SetHTMLTemplate(templ)
|
engine().SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
|
||||||
func NoRoute(handlers ...gin.HandlerFunc) {
|
func NoRoute(handlers ...gin.HandlerFunc) {
|
||||||
engine().NoRoute(handlers...)
|
engine().NoRoute(handlers...)
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
|||||||
return engine().StaticFS(relativePath, fs)
|
return engine().StaticFS(relativePath, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be
|
// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be
|
||||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
// For example, this is the right place for a logger or error management middleware.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
||||||
@ -145,7 +145,7 @@ func RunTLS(addr, certFile, keyFile string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunUnix attaches to a http.Server and starts listening and serving HTTP requests
|
// RunUnix attaches to a http.Server and starts listening and serving HTTP requests
|
||||||
// through the specified unix socket (ie. a file)
|
// through the specified unix socket (i.e. a file)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func RunUnix(file string) (err error) {
|
func RunUnix(file string) (err error) {
|
||||||
return engine().RunUnix(file)
|
return engine().RunUnix(file)
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -21,7 +23,15 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRequest(t *testing.T, url string) {
|
// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty)
|
||||||
|
// params[1]=response status (custom compare status) default:"200 OK"
|
||||||
|
// params[2]=response body (custom compare content) default:"it worked"
|
||||||
|
func testRequest(t *testing.T, params ...string) {
|
||||||
|
|
||||||
|
if len(params) == 0 {
|
||||||
|
t.Fatal("url cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -29,14 +39,27 @@ func testRequest(t *testing.T, url string) {
|
|||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
|
||||||
resp, err := client.Get(url)
|
resp, err := client.Get(params[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, ioerr := ioutil.ReadAll(resp.Body)
|
body, ioerr := ioutil.ReadAll(resp.Body)
|
||||||
assert.NoError(t, ioerr)
|
assert.NoError(t, ioerr)
|
||||||
assert.Equal(t, "it worked", string(body), "resp body should match")
|
|
||||||
assert.Equal(t, "200 OK", resp.Status, "should get a 200")
|
var responseStatus = "200 OK"
|
||||||
|
if len(params) > 1 && params[1] != "" {
|
||||||
|
responseStatus = params[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseBody = "it worked"
|
||||||
|
if len(params) > 2 && params[2] != "" {
|
||||||
|
responseBody = params[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, responseStatus, resp.Status, "should get a "+responseStatus)
|
||||||
|
if responseStatus == "200 OK" {
|
||||||
|
assert.Equal(t, responseBody, string(body), "resp body should match")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunEmpty(t *testing.T) {
|
func TestRunEmpty(t *testing.T) {
|
||||||
@ -54,6 +77,81 @@ func TestRunEmpty(t *testing.T) {
|
|||||||
testRequest(t, "http://localhost:8080/example")
|
testRequest(t, "http://localhost:8080/example")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBadTrustedCIDRs(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
assert.Error(t, router.SetTrustedProxies([]string{"hello/world"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* legacy tests
|
||||||
|
func TestBadTrustedCIDRsForRun(t *testing.T) {
|
||||||
|
os.Setenv("PORT", "")
|
||||||
|
router := New()
|
||||||
|
router.TrustedProxies = []string{"hello/world"}
|
||||||
|
assert.Error(t, router.Run(":8080"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadTrustedCIDRsForRunUnix(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.TrustedProxies = []string{"hello/world"}
|
||||||
|
|
||||||
|
unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test")
|
||||||
|
|
||||||
|
defer os.Remove(unixTestSocket)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
assert.Error(t, router.RunUnix(unixTestSocket))
|
||||||
|
}()
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadTrustedCIDRsForRunFd(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.TrustedProxies = []string{"hello/world"}
|
||||||
|
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
listener, err := net.ListenTCP("tcp", addr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
socketFile, err := listener.File()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
assert.Error(t, router.RunFd(int(socketFile.Fd())))
|
||||||
|
}()
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadTrustedCIDRsForRunListener(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.TrustedProxies = []string{"hello/world"}
|
||||||
|
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
listener, err := net.ListenTCP("tcp", addr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
go func() {
|
||||||
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
assert.Error(t, router.RunListener(listener))
|
||||||
|
}()
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadTrustedCIDRsForRunTLS(t *testing.T) {
|
||||||
|
os.Setenv("PORT", "")
|
||||||
|
router := New()
|
||||||
|
router.TrustedProxies = []string{"hello/world"}
|
||||||
|
assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func TestRunTLS(t *testing.T) {
|
func TestRunTLS(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
go func() {
|
go func() {
|
||||||
@ -146,7 +244,7 @@ func TestRunWithPort(t *testing.T) {
|
|||||||
func TestUnixSocket(t *testing.T) {
|
func TestUnixSocket(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
unixTestSocket := "/tmp/unix_unit_test"
|
unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test")
|
||||||
|
|
||||||
defer os.Remove(unixTestSocket)
|
defer os.Remove(unixTestSocket)
|
||||||
|
|
||||||
@ -184,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
|
|||||||
listener, err := net.ListenTCP("tcp", addr)
|
listener, err := net.ListenTCP("tcp", addr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
socketFile, err := listener.File()
|
socketFile, err := listener.File()
|
||||||
|
if isWindows() {
|
||||||
|
// not supported by windows, it is unimplemented now
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if socketFile == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
@ -304,3 +411,153 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
|||||||
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
||||||
assert.Equal(t, 200, w.Code, "should get a 200")
|
assert.Equal(t, 200, w.Code, "should get a 200")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeRunDynamicRouting(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
|
||||||
|
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
|
||||||
|
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
|
||||||
|
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
|
||||||
|
router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
|
||||||
|
router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
|
||||||
|
router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") })
|
||||||
|
router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") })
|
||||||
|
router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
|
||||||
|
router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") })
|
||||||
|
router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") })
|
||||||
|
router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") })
|
||||||
|
router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
|
||||||
|
router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
|
||||||
|
router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") })
|
||||||
|
router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") })
|
||||||
|
router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") })
|
||||||
|
router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") })
|
||||||
|
router.GET("/get/abc", func(c *Context) { c.String(http.StatusOK, "/get/abc") })
|
||||||
|
router.GET("/get/:param", func(c *Context) { c.String(http.StatusOK, "/get/:param") })
|
||||||
|
router.GET("/get/abc/123abc", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc") })
|
||||||
|
router.GET("/get/abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8") })
|
||||||
|
router.GET("/get/abc/123abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/:param") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/1234", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/:param") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/1234/ffas", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/ffas") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/1234/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/:param") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/1234/kkdd/12c", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/12c") })
|
||||||
|
router.GET("/get/abc/123abc/xxx8/1234/kkdd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/:param") })
|
||||||
|
router.GET("/get/abc/:param/test", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param/test") })
|
||||||
|
router.GET("/get/abc/123abd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abd/:param") })
|
||||||
|
router.GET("/get/abc/123abddd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abddd/:param") })
|
||||||
|
router.GET("/get/abc/123/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123/:param") })
|
||||||
|
router.GET("/get/abc/123abg/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abg/:param") })
|
||||||
|
router.GET("/get/abc/123abf/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abf/:param") })
|
||||||
|
router.GET("/get/abc/123abfff/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abfff/:param") })
|
||||||
|
|
||||||
|
ts := httptest.NewServer(router)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
testRequest(t, ts.URL+"/", "", "home")
|
||||||
|
testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx")
|
||||||
|
testRequest(t, ts.URL+"/ab/ab", "", "/ab/*xx")
|
||||||
|
testRequest(t, ts.URL+"/all", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f")
|
||||||
|
testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee")
|
||||||
|
testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff")
|
||||||
|
testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg")
|
||||||
|
testRequest(t, ts.URL+"/c/d/e/f/g/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
|
||||||
|
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
|
||||||
|
testRequest(t, ts.URL+"/a", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/d", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/ad", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/dd", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/aa", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/aaa", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/aaa/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/ab", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/abb", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/abb/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/dddaa", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/allxxxx", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/alldd", "", "/:cc")
|
||||||
|
testRequest(t, ts.URL+"/cc/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/ccc/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/deedwjfs/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/acllcc/cc", "", "/:cc/cc")
|
||||||
|
testRequest(t, ts.URL+"/get/test/abc/", "", "/get/test/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/testaa/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/te/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/xx/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/tt/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/a/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/t/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/")
|
||||||
|
testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test")
|
||||||
|
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
|
testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
|
testRequest(t, ts.URL+"/something/se/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
|
testRequest(t, ts.URL+"/something/s/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
|
testRequest(t, ts.URL+"/something/secondthing/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
|
testRequest(t, ts.URL+"/get/abc", "", "/get/abc")
|
||||||
|
testRequest(t, ts.URL+"/get/a", "", "/get/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abz", "", "/get/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/12a", "", "/get/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abcd", "", "/get/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc", "", "/get/abc/123abc")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/12", "", "/get/abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123ab", "", "/get/abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/xyz", "", "/get/abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abcddxx", "", "/get/abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8", "", "/get/abc/123abc/xxx8")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/x", "", "/get/abc/123abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx", "", "/get/abc/123abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/abc", "", "/get/abc/123abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8xxas", "", "/get/abc/123abc/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234", "", "/get/abc/123abc/xxx8/1234")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1", "", "/get/abc/123abc/xxx8/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/123", "", "/get/abc/123abc/xxx8/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/78k", "", "/get/abc/123abc/xxx8/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234xxxd", "", "/get/abc/123abc/xxx8/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas", "", "/get/abc/123abc/xxx8/1234/ffas")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/f", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffa", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kka", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas321", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c", "", "/get/abc/123abc/xxx8/1234/kkdd/12c")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/1", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12b", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/34", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/12/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abdd/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abdddf/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123ab/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abgg/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abff/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abffff/test", "", "/get/abc/:param/test")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abd/test", "", "/get/abc/123abd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abddd/test", "", "/get/abc/123abddd/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123/test22", "", "/get/abc/123/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abg/test", "", "/get/abc/123abg/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
|
||||||
|
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
|
||||||
|
// 404 not found
|
||||||
|
testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/a/dd", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
|
||||||
|
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWindows() bool {
|
||||||
|
return runtime.GOOS == "windows"
|
||||||
|
}
|
||||||
|
|||||||
166
gin_test.go
166
gin_test.go
@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatAsDate(t time.Time) string {
|
func formatAsDate(t time.Time) string {
|
||||||
@ -41,7 +43,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine
|
|||||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||||
})
|
})
|
||||||
router.GET("/raw", func(c *Context) {
|
router.GET("/raw", func(c *Context) {
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
c.HTML(http.StatusOK, "raw.tmpl", map[string]any{
|
||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -78,6 +80,44 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
|
|||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestH2c(t *testing.T) {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
r := Default()
|
||||||
|
r.UseH2C = true
|
||||||
|
r.GET("/", func(c *Context) {
|
||||||
|
c.String(200, "<h1>Hello world</h1>")
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
err := http.Serve(ln, r.Handler())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
url := "http://" + ln.Addr().String() + "/"
|
||||||
|
|
||||||
|
http := http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
AllowHTTP: true,
|
||||||
|
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
return net.Dial(netw, addr)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlobTestMode(t *testing.T) {
|
func TestLoadHTMLGlobTestMode(t *testing.T) {
|
||||||
ts := setupHTMLFiles(
|
ts := setupHTMLFiles(
|
||||||
t,
|
t,
|
||||||
@ -162,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -280,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRoute(t *testing.T) {
|
func TestAddRoute(t *testing.T) {
|
||||||
@ -394,7 +434,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRebuild404Handlers(t *testing.T) {
|
func TestRebuild404Handlers(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
||||||
@ -428,7 +467,7 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
|||||||
compareFunc(t, router.allNoMethod[2], middleware0)
|
compareFunc(t, router.allNoMethod[2], middleware0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareFunc(t *testing.T, a, b interface{}) {
|
func compareFunc(t *testing.T, a, b any) {
|
||||||
sf1 := reflect.ValueOf(a)
|
sf1 := reflect.ValueOf(a)
|
||||||
sf2 := reflect.ValueOf(b)
|
sf2 := reflect.ValueOf(b)
|
||||||
if sf1.Pointer() != sf2.Pointer() {
|
if sf1.Pointer() != sf2.Pointer() {
|
||||||
@ -490,7 +529,7 @@ func TestEngineHandleContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
w := performRequest(r, "GET", "/")
|
w := PerformRequest(r, "GET", "/")
|
||||||
assert.Equal(t, 301, w.Code)
|
assert.Equal(t, 301, w.Code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -523,7 +562,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, expectValue, w.Body.Len())
|
assert.Equal(t, expectValue, w.Body.Len())
|
||||||
})
|
})
|
||||||
@ -532,6 +571,119 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
|||||||
assert.Equal(t, int64(expectValue), middlewareCounter)
|
assert.Equal(t, int64(expectValue), middlewareCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
// valid ipv4 cidr
|
||||||
|
{
|
||||||
|
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")}
|
||||||
|
err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid ipv4 cidr
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies([]string{"192.168.1.33/33"})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid ipv4 address
|
||||||
|
{
|
||||||
|
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")}
|
||||||
|
|
||||||
|
err := r.SetTrustedProxies([]string{"192.168.1.33"})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid ipv4 address
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies([]string{"192.168.1.256"})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid ipv6 address
|
||||||
|
{
|
||||||
|
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
|
||||||
|
err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid ipv6 address
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid ipv6 cidr
|
||||||
|
{
|
||||||
|
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")}
|
||||||
|
err := r.SetTrustedProxies([]string{"::/0"})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid ipv6 cidr
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid combination
|
||||||
|
{
|
||||||
|
expectedTrustedCIDRs := []*net.IPNet{
|
||||||
|
parseCIDR("::/0"),
|
||||||
|
parseCIDR("192.168.0.0/16"),
|
||||||
|
parseCIDR("172.16.0.1/32"),
|
||||||
|
}
|
||||||
|
err := r.SetTrustedProxies([]string{
|
||||||
|
"::/0",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"172.16.0.1",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid combination
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies([]string{
|
||||||
|
"::/0",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"172.16.0.256",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nil value
|
||||||
|
{
|
||||||
|
err := r.SetTrustedProxies(nil)
|
||||||
|
|
||||||
|
assert.Nil(t, r.trustedCIDRs)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCIDR(cidr string) *net.IPNet {
|
||||||
|
_, parsedCIDR, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return parsedCIDR
|
||||||
|
}
|
||||||
|
|
||||||
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
|
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
|
||||||
for _, gotRoute := range gotRoutes {
|
for _, gotRoute := range gotRoutes {
|
||||||
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {
|
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {
|
||||||
|
|||||||
@ -296,13 +296,13 @@ func TestShouldBindUri(t *testing.T) {
|
|||||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||||
var person Person
|
var person Person
|
||||||
assert.NoError(t, c.ShouldBindUri(&person))
|
assert.NoError(t, c.ShouldBindUri(&person))
|
||||||
assert.True(t, "" != person.Name)
|
assert.True(t, person.Name != "")
|
||||||
assert.True(t, "" != person.ID)
|
assert.True(t, person.ID != "")
|
||||||
c.String(http.StatusOK, "ShouldBindUri test OK")
|
c.String(http.StatusOK, "ShouldBindUri test OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
path, _ := exampleFromPath("/rest/:name/:id")
|
path, _ := exampleFromPath("/rest/:name/:id")
|
||||||
w := performRequest(router, http.MethodGet, path)
|
w := PerformRequest(router, http.MethodGet, path)
|
||||||
assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
|
assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
@ -318,13 +318,13 @@ func TestBindUri(t *testing.T) {
|
|||||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||||
var person Person
|
var person Person
|
||||||
assert.NoError(t, c.BindUri(&person))
|
assert.NoError(t, c.BindUri(&person))
|
||||||
assert.True(t, "" != person.Name)
|
assert.True(t, person.Name != "")
|
||||||
assert.True(t, "" != person.ID)
|
assert.True(t, person.ID != "")
|
||||||
c.String(http.StatusOK, "BindUri test OK")
|
c.String(http.StatusOK, "BindUri test OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
path, _ := exampleFromPath("/rest/:name/:id")
|
path, _ := exampleFromPath("/rest/:name/:id")
|
||||||
w := performRequest(router, http.MethodGet, path)
|
w := PerformRequest(router, http.MethodGet, path)
|
||||||
assert.Equal(t, "BindUri test OK", w.Body.String())
|
assert.Equal(t, "BindUri test OK", w.Body.String())
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
@ -342,7 +342,7 @@ func TestBindUriError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
path1, _ := exampleFromPath("/new/rest/:num")
|
path1, _ := exampleFromPath("/new/rest/:num")
|
||||||
w1 := performRequest(router, http.MethodGet, path1)
|
w1 := PerformRequest(router, http.MethodGet, path1)
|
||||||
assert.Equal(t, http.StatusBadRequest, w1.Code)
|
assert.Equal(t, http.StatusBadRequest, w1.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) {
|
|||||||
go readWriteKeys(c.Copy())
|
go readWriteKeys(c.Copy())
|
||||||
c.String(http.StatusOK, "run OK, no panics")
|
c.String(http.StatusOK, "run OK, no panics")
|
||||||
})
|
})
|
||||||
w := performRequest(router, http.MethodGet, "/test/copy/race")
|
w := PerformRequest(router, http.MethodGet, "/test/copy/race")
|
||||||
assert.Equal(t, "run OK, no panics", w.Body.String())
|
assert.Equal(t, "run OK, no panics", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +389,7 @@ func TestGithubAPI(t *testing.T) {
|
|||||||
|
|
||||||
for _, route := range githubAPI {
|
for _, route := range githubAPI {
|
||||||
path, values := exampleFromPath(route.path)
|
path, values := exampleFromPath(route.path)
|
||||||
w := performRequest(router, route.method, path)
|
w := PerformRequest(router, route.method, path)
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
|
assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
|
||||||
|
|||||||
33
go.mod
33
go.mod
@ -1,14 +1,31 @@
|
|||||||
module github.com/gin-gonic/gin
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
go 1.13
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/sse v0.1.0
|
github.com/gin-contrib/sse v0.1.0
|
||||||
github.com/go-playground/validator/v10 v10.4.1
|
github.com/go-playground/validator/v10 v10.10.0
|
||||||
github.com/golang/protobuf v1.3.3
|
github.com/goccy/go-json v0.9.8
|
||||||
github.com/json-iterator/go v1.1.9
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.12
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/pelletier/go-toml/v2 v2.0.2
|
||||||
github.com/ugorji/go/codec v1.1.7
|
github.com/stretchr/testify v1.8.0
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
github.com/ugorji/go/codec v1.2.7
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||||
|
google.golang.org/protobuf v1.28.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
|
||||||
|
golang.org/x/text v0.3.6 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
108
go.sum
108
go.sum
@ -1,3 +1,4 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -5,48 +6,83 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@ -5,16 +5,17 @@
|
|||||||
package bytesconv
|
package bytesconv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StringToBytes converts string to byte slice without a memory allocation.
|
// StringToBytes converts string to byte slice without a memory allocation.
|
||||||
func StringToBytes(s string) (b []byte) {
|
func StringToBytes(s string) []byte {
|
||||||
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
|
return *(*[]byte)(unsafe.Pointer(
|
||||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
&struct {
|
||||||
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
|
string
|
||||||
return b
|
Cap int
|
||||||
|
}{s, len(s)},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BytesToString converts byte slice to string without a memory allocation.
|
// BytesToString converts byte slice to string without a memory allocation.
|
||||||
|
|||||||
23
internal/json/go_json.go
Normal file
23
internal/json/go_json.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go_json
|
||||||
|
// +build go_json
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import json "github.com/goccy/go-json"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
||||||
@ -2,7 +2,8 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !jsoniter
|
//go:build !jsoniter && !go_json
|
||||||
|
// +build !jsoniter,!go_json
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build jsoniter
|
||||||
// +build jsoniter
|
// +build jsoniter
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
|||||||
15
logger.go
15
logger.go
@ -44,7 +44,7 @@ type LoggerConfig struct {
|
|||||||
// Optional. Default value is gin.DefaultWriter.
|
// Optional. Default value is gin.DefaultWriter.
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
|
|
||||||
// SkipPaths is a url path array which logs are not written.
|
// SkipPaths is an url path array which logs are not written.
|
||||||
// Optional.
|
// Optional.
|
||||||
SkipPaths []string
|
SkipPaths []string
|
||||||
}
|
}
|
||||||
@ -70,12 +70,12 @@ type LogFormatterParams struct {
|
|||||||
Path string
|
Path string
|
||||||
// ErrorMessage is set if error has occurred in processing the request.
|
// ErrorMessage is set if error has occurred in processing the request.
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
// isTerm shows whether does gin's output descriptor refers to a terminal.
|
// isTerm shows whether gin's output descriptor refers to a terminal.
|
||||||
isTerm bool
|
isTerm bool
|
||||||
// BodySize is the size of the Response Body
|
// BodySize is the size of the Response Body
|
||||||
BodySize int
|
BodySize int
|
||||||
// Keys are the keys set on the request's context.
|
// Keys are the keys set on the request's context.
|
||||||
Keys map[string]interface{}
|
Keys map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
||||||
@ -138,8 +138,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if param.Latency > time.Minute {
|
if param.Latency > time.Minute {
|
||||||
// Truncate in a golang < 1.8 safe way
|
param.Latency = param.Latency.Truncate(time.Second)
|
||||||
param.Latency = param.Latency - param.Latency%time.Second
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
|
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
|
||||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||||
@ -162,12 +161,12 @@ func ForceConsoleColor() {
|
|||||||
consoleColorMode = forceColor
|
consoleColorMode = forceColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorLogger returns a handlerfunc for any error type.
|
// ErrorLogger returns a HandlerFunc for any error type.
|
||||||
func ErrorLogger() HandlerFunc {
|
func ErrorLogger() HandlerFunc {
|
||||||
return ErrorLoggerT(ErrorTypeAny)
|
return ErrorLoggerT(ErrorTypeAny)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorLoggerT returns a handlerfunc for a given error type.
|
// ErrorLoggerT returns a HandlerFunc for a given error type.
|
||||||
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -179,7 +178,7 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
|
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
|
||||||
// By default gin.DefaultWriter = os.Stdout.
|
// By default, gin.DefaultWriter = os.Stdout.
|
||||||
func Logger() HandlerFunc {
|
func Logger() HandlerFunc {
|
||||||
return LoggerWithConfig(LoggerConfig{})
|
return LoggerWithConfig(LoggerConfig{})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ func TestLogger(t *testing.T) {
|
|||||||
router.HEAD("/example", func(c *Context) {})
|
router.HEAD("/example", func(c *Context) {})
|
||||||
router.OPTIONS("/example", func(c *Context) {})
|
router.OPTIONS("/example", func(c *Context) {})
|
||||||
|
|
||||||
performRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, "GET", "/example?a=100")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), "GET")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
@ -41,43 +41,43 @@ func TestLogger(t *testing.T) {
|
|||||||
// like integration tests because they test the whole logging process rather
|
// like integration tests because they test the whole logging process rather
|
||||||
// than individual functions. Im not sure where these should go.
|
// than individual functions. Im not sure where these should go.
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "POST", "/example")
|
PerformRequest(router, "POST", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "POST")
|
assert.Contains(t, buffer.String(), "POST")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "PUT", "/example")
|
PerformRequest(router, "PUT", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PUT")
|
assert.Contains(t, buffer.String(), "PUT")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "DELETE", "/example")
|
PerformRequest(router, "DELETE", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "DELETE")
|
assert.Contains(t, buffer.String(), "DELETE")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "PATCH", "/example")
|
PerformRequest(router, "PATCH", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PATCH")
|
assert.Contains(t, buffer.String(), "PATCH")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "HEAD", "/example")
|
PerformRequest(router, "HEAD", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "HEAD")
|
assert.Contains(t, buffer.String(), "HEAD")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "OPTIONS", "/example")
|
PerformRequest(router, "OPTIONS", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "GET", "/notfound")
|
PerformRequest(router, "GET", "/notfound")
|
||||||
assert.Contains(t, buffer.String(), "404")
|
assert.Contains(t, buffer.String(), "404")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), "GET")
|
||||||
assert.Contains(t, buffer.String(), "/notfound")
|
assert.Contains(t, buffer.String(), "/notfound")
|
||||||
@ -95,7 +95,7 @@ func TestLoggerWithConfig(t *testing.T) {
|
|||||||
router.HEAD("/example", func(c *Context) {})
|
router.HEAD("/example", func(c *Context) {})
|
||||||
router.OPTIONS("/example", func(c *Context) {})
|
router.OPTIONS("/example", func(c *Context) {})
|
||||||
|
|
||||||
performRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, "GET", "/example?a=100")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), "GET")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
@ -105,43 +105,43 @@ func TestLoggerWithConfig(t *testing.T) {
|
|||||||
// like integration tests because they test the whole logging process rather
|
// like integration tests because they test the whole logging process rather
|
||||||
// than individual functions. Im not sure where these should go.
|
// than individual functions. Im not sure where these should go.
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "POST", "/example")
|
PerformRequest(router, "POST", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "POST")
|
assert.Contains(t, buffer.String(), "POST")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "PUT", "/example")
|
PerformRequest(router, "PUT", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PUT")
|
assert.Contains(t, buffer.String(), "PUT")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "DELETE", "/example")
|
PerformRequest(router, "DELETE", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "DELETE")
|
assert.Contains(t, buffer.String(), "DELETE")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "PATCH", "/example")
|
PerformRequest(router, "PATCH", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PATCH")
|
assert.Contains(t, buffer.String(), "PATCH")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "HEAD", "/example")
|
PerformRequest(router, "HEAD", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "HEAD")
|
assert.Contains(t, buffer.String(), "HEAD")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "OPTIONS", "/example")
|
PerformRequest(router, "OPTIONS", "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "GET", "/notfound")
|
PerformRequest(router, "GET", "/notfound")
|
||||||
assert.Contains(t, buffer.String(), "404")
|
assert.Contains(t, buffer.String(), "404")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), "GET")
|
||||||
assert.Contains(t, buffer.String(), "/notfound")
|
assert.Contains(t, buffer.String(), "/notfound")
|
||||||
@ -169,7 +169,7 @@ func TestLoggerWithFormatter(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
router.GET("/example", func(c *Context) {})
|
router.GET("/example", func(c *Context) {})
|
||||||
performRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, "GET", "/example?a=100")
|
||||||
|
|
||||||
// output test
|
// output test
|
||||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||||
@ -181,10 +181,12 @@ func TestLoggerWithFormatter(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||||
var gotParam LogFormatterParams
|
var gotParam LogFormatterParams
|
||||||
var gotKeys map[string]interface{}
|
var gotKeys map[string]any
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
router := New()
|
router := New()
|
||||||
|
router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs()
|
||||||
|
|
||||||
router.Use(LoggerWithConfig(LoggerConfig{
|
router.Use(LoggerWithConfig(LoggerConfig{
|
||||||
Output: buffer,
|
Output: buffer,
|
||||||
Formatter: func(param LogFormatterParams) string {
|
Formatter: func(param LogFormatterParams) string {
|
||||||
@ -206,8 +208,9 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
// set dummy ClientIP
|
// set dummy ClientIP
|
||||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||||
gotKeys = c.Keys
|
gotKeys = c.Keys
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
})
|
})
|
||||||
performRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, "GET", "/example?a=100")
|
||||||
|
|
||||||
// output test
|
// output test
|
||||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||||
@ -226,7 +229,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||||
assert.Empty(t, gotParam.ErrorMessage)
|
assert.Empty(t, gotParam.ErrorMessage)
|
||||||
assert.Equal(t, gotKeys, gotParam.Keys)
|
assert.Equal(t, gotKeys, gotParam.Keys)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultLogFormatter(t *testing.T) {
|
func TestDefaultLogFormatter(t *testing.T) {
|
||||||
@ -280,7 +282,6 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
|
||||||
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorForMethod(t *testing.T) {
|
func TestColorForMethod(t *testing.T) {
|
||||||
@ -367,15 +368,15 @@ func TestErrorLogger(t *testing.T) {
|
|||||||
c.String(http.StatusInternalServerError, "hola!")
|
c.String(http.StatusInternalServerError, "hola!")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, "GET", "/error")
|
w := PerformRequest(router, "GET", "/error")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/abort")
|
w = PerformRequest(router, "GET", "/abort")
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/print")
|
w = PerformRequest(router, "GET", "/print")
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||||
}
|
}
|
||||||
@ -387,11 +388,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
|||||||
router.GET("/logged", func(c *Context) {})
|
router.GET("/logged", func(c *Context) {})
|
||||||
router.GET("/skipped", func(c *Context) {})
|
router.GET("/skipped", func(c *Context) {})
|
||||||
|
|
||||||
performRequest(router, "GET", "/logged")
|
PerformRequest(router, "GET", "/logged")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "GET", "/skipped")
|
PerformRequest(router, "GET", "/skipped")
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,11 +406,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
|||||||
router.GET("/logged", func(c *Context) {})
|
router.GET("/logged", func(c *Context) {})
|
||||||
router.GET("/skipped", func(c *Context) {})
|
router.GET("/skipped", func(c *Context) {})
|
||||||
|
|
||||||
performRequest(router, "GET", "/logged")
|
PerformRequest(router, "GET", "/logged")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
performRequest(router, "GET", "/skipped")
|
PerformRequest(router, "GET", "/skipped")
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
|
|||||||
signature += " XX "
|
signature += " XX "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
|||||||
signature += " X "
|
signature += " X "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
|||||||
signature += " XX "
|
signature += " XX "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
|||||||
func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||||
signature := ""
|
signature := ""
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
|
// NoMethod disabled
|
||||||
router.HandleMethodNotAllowed = false
|
router.HandleMethodNotAllowed = false
|
||||||
|
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
signature += "A"
|
signature += "A"
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -144,8 +147,9 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
|||||||
router.POST("/", func(c *Context) {
|
router.POST("/", func(c *Context) {
|
||||||
signature += " XX "
|
signature += " XX "
|
||||||
})
|
})
|
||||||
|
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
@ -171,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
@ -186,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
c.AbortWithStatus(http.StatusGone)
|
c.AbortWithStatus(http.StatusGone)
|
||||||
signature += "B"
|
signature += "B"
|
||||||
|
|
||||||
})
|
})
|
||||||
router.GET("/", func(c *Context) {
|
router.GET("/", func(c *Context) {
|
||||||
signature += "C"
|
signature += "C"
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusGone, w.Code)
|
assert.Equal(t, http.StatusGone, w.Code)
|
||||||
@ -216,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
|||||||
signature += "C"
|
signature += "C"
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
@ -243,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
w := PerformRequest(router, "GET", "/")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
||||||
|
|||||||
15
mode.go
15
mode.go
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -41,8 +42,10 @@ var DefaultWriter io.Writer = os.Stdout
|
|||||||
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
||||||
var DefaultErrorWriter io.Writer = os.Stderr
|
var DefaultErrorWriter io.Writer = os.Stderr
|
||||||
|
|
||||||
var ginMode = debugCode
|
var (
|
||||||
var modeName = DebugMode
|
ginMode = debugCode
|
||||||
|
modeName = DebugMode
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mode := os.Getenv(EnvGinMode)
|
mode := os.Getenv(EnvGinMode)
|
||||||
@ -52,8 +55,12 @@ func init() {
|
|||||||
// SetMode sets gin mode according to input string.
|
// SetMode sets gin mode according to input string.
|
||||||
func SetMode(value string) {
|
func SetMode(value string) {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
if flag.Lookup("test.v") != nil {
|
||||||
|
value = TestMode
|
||||||
|
} else {
|
||||||
value = DebugMode
|
value = DebugMode
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch value {
|
switch value {
|
||||||
case DebugMode:
|
case DebugMode:
|
||||||
@ -63,7 +70,7 @@ func SetMode(value string) {
|
|||||||
case TestMode:
|
case TestMode:
|
||||||
ginMode = testCode
|
ginMode = testCode
|
||||||
default:
|
default:
|
||||||
panic("gin mode unknown: " + value)
|
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||||
}
|
}
|
||||||
|
|
||||||
modeName = value
|
modeName = value
|
||||||
@ -86,7 +93,7 @@ func EnableJsonDecoderDisallowUnknownFields() {
|
|||||||
binding.EnableDecoderDisallowUnknownFields = true
|
binding.EnableDecoderDisallowUnknownFields = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode returns currently gin mode.
|
// Mode returns current gin mode.
|
||||||
func Mode() string {
|
func Mode() string {
|
||||||
return modeName
|
return modeName
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
|
|||||||
assert.Equal(t, TestMode, Mode())
|
assert.Equal(t, TestMode, Mode())
|
||||||
os.Unsetenv(EnvGinMode)
|
os.Unsetenv(EnvGinMode)
|
||||||
|
|
||||||
|
SetMode("")
|
||||||
|
assert.Equal(t, testCode, ginMode)
|
||||||
|
assert.Equal(t, TestMode, Mode())
|
||||||
|
|
||||||
|
tmp := flag.CommandLine
|
||||||
|
flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
SetMode("")
|
SetMode("")
|
||||||
assert.Equal(t, debugCode, ginMode)
|
assert.Equal(t, debugCode, ginMode)
|
||||||
assert.Equal(t, DebugMode, Mode())
|
assert.Equal(t, DebugMode, Mode())
|
||||||
|
flag.CommandLine = tmp
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
assert.Equal(t, debugCode, ginMode)
|
assert.Equal(t, debugCode, ginMode)
|
||||||
|
|||||||
14
recovery.go
14
recovery.go
@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -27,7 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RecoveryFunc defines the function passable to CustomRecovery.
|
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||||
type RecoveryFunc func(c *Context, err interface{})
|
type RecoveryFunc func(c *Context, err any)
|
||||||
|
|
||||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||||
func Recovery() HandlerFunc {
|
func Recovery() HandlerFunc {
|
||||||
@ -60,7 +61,8 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
|||||||
// condition that warrants a panic stack trace.
|
// condition that warrants a panic stack trace.
|
||||||
var brokenPipe bool
|
var brokenPipe bool
|
||||||
if ne, ok := err.(*net.OpError); ok {
|
if ne, ok := err.(*net.OpError); ok {
|
||||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
var se *os.SyscallError
|
||||||
|
if errors.As(ne, &se) {
|
||||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||||
brokenPipe = true
|
brokenPipe = true
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultHandleRecovery(c *Context, err interface{}) {
|
func defaultHandleRecovery(c *Context, err any) {
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ func function(pc uintptr) []byte {
|
|||||||
// runtime/debug.*T·ptrmethod
|
// runtime/debug.*T·ptrmethod
|
||||||
// and want
|
// and want
|
||||||
// *T.ptrmethod
|
// *T.ptrmethod
|
||||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
// Also the package path might contain dot (e.g. code.google.com/...),
|
||||||
// so first eliminate the path prefix
|
// so first eliminate the path prefix
|
||||||
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
||||||
name = name[lastSlash+1:]
|
name = name[lastSlash+1:]
|
||||||
@ -165,7 +167,7 @@ func function(pc uintptr) []byte {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeFormat returns a customized time string for logger.
|
||||||
func timeFormat(t time.Time) string {
|
func timeFormat(t time.Time) string {
|
||||||
timeString := t.Format("2006/01/02 - 15:04:05")
|
return t.Format("2006/01/02 - 15:04:05")
|
||||||
return timeString
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func TestPanicClean(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery",
|
w := PerformRequest(router, "GET", "/recovery",
|
||||||
header{
|
header{
|
||||||
Key: "Host",
|
Key: "Host",
|
||||||
Value: "www.google.com",
|
Value: "www.google.com",
|
||||||
@ -57,7 +57,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@ -68,7 +68,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = performRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@ -85,21 +85,21 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSource(t *testing.T) {
|
func TestSource(t *testing.T) {
|
||||||
bs := source(nil, 0)
|
bs := source(nil, 0)
|
||||||
assert.Equal(t, []byte("???"), bs)
|
assert.Equal(t, dunno, bs)
|
||||||
|
|
||||||
in := [][]byte{
|
in := [][]byte{
|
||||||
[]byte("Hello world."),
|
[]byte("Hello world."),
|
||||||
[]byte("Hi, gin.."),
|
[]byte("Hi, gin.."),
|
||||||
}
|
}
|
||||||
bs = source(in, 10)
|
bs = source(in, 10)
|
||||||
assert.Equal(t, []byte("???"), bs)
|
assert.Equal(t, dunno, bs)
|
||||||
|
|
||||||
bs = source(in, 1)
|
bs = source(in, 1)
|
||||||
assert.Equal(t, []byte("Hello world."), bs)
|
assert.Equal(t, []byte("Hello world."), bs)
|
||||||
@ -107,7 +107,7 @@ func TestSource(t *testing.T) {
|
|||||||
|
|
||||||
func TestFunction(t *testing.T) {
|
func TestFunction(t *testing.T) {
|
||||||
bs := function(1)
|
bs := function(1)
|
||||||
assert.Equal(t, []byte("???"), bs)
|
assert.Equal(t, dunno, bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPanicWithBrokenPipe asserts that recovery specifically handles
|
// TestPanicWithBrokenPipe asserts that recovery specifically handles
|
||||||
@ -122,7 +122,6 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
|||||||
|
|
||||||
for errno, expectMsg := range expectMsgs {
|
for errno, expectMsg := range expectMsgs {
|
||||||
t.Run(expectMsg, func(t *testing.T) {
|
t.Run(expectMsg, func(t *testing.T) {
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
router := New()
|
router := New()
|
||||||
@ -137,7 +136,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
|||||||
panic(e)
|
panic(e)
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, expectCode, w.Code)
|
assert.Equal(t, expectCode, w.Code)
|
||||||
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
||||||
@ -149,7 +148,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
|||||||
errBuffer := new(bytes.Buffer)
|
errBuffer := new(bytes.Buffer)
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
router := New()
|
router := New()
|
||||||
handleRecovery := func(c *Context, err interface{}) {
|
handleRecovery := func(c *Context, err any) {
|
||||||
errBuffer.WriteString(err.(string))
|
errBuffer.WriteString(err.(string))
|
||||||
c.AbortWithStatus(http.StatusBadRequest)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@ -158,7 +157,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@ -169,7 +168,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = performRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@ -184,7 +183,7 @@ func TestCustomRecovery(t *testing.T) {
|
|||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
router := New()
|
router := New()
|
||||||
DefaultErrorWriter = buffer
|
DefaultErrorWriter = buffer
|
||||||
handleRecovery := func(c *Context, err interface{}) {
|
handleRecovery := func(c *Context, err any) {
|
||||||
errBuffer.WriteString(err.(string))
|
errBuffer.WriteString(err.(string))
|
||||||
c.AbortWithStatus(http.StatusBadRequest)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@ -193,7 +192,7 @@ func TestCustomRecovery(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@ -204,7 +203,7 @@ func TestCustomRecovery(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = performRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@ -219,7 +218,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
router := New()
|
router := New()
|
||||||
DefaultErrorWriter = buffer
|
DefaultErrorWriter = buffer
|
||||||
handleRecovery := func(c *Context, err interface{}) {
|
handleRecovery := func(c *Context, err any) {
|
||||||
errBuffer.WriteString(err.(string))
|
errBuffer.WriteString(err.(string))
|
||||||
c.AbortWithStatus(http.StatusBadRequest)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@ -228,7 +227,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@ -239,7 +238,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = performRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
|
|||||||
10
render/any.go
Normal file
10
render/any.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2021 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
type any = interface{}
|
||||||
@ -20,7 +20,7 @@ type Delims struct {
|
|||||||
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
||||||
type HTMLRender interface {
|
type HTMLRender interface {
|
||||||
// Instance returns an HTML instance.
|
// Instance returns an HTML instance.
|
||||||
Instance(string, interface{}) Render
|
Instance(string, any) Render
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTMLProduction contains template reference and its delims.
|
// HTMLProduction contains template reference and its delims.
|
||||||
@ -41,13 +41,13 @@ type HTMLDebug struct {
|
|||||||
type HTML struct {
|
type HTML struct {
|
||||||
Template *template.Template
|
Template *template.Template
|
||||||
Name string
|
Name string
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||||
|
|
||||||
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
|
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
|
||||||
func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
func (r HTMLProduction) Instance(name string, data any) Render {
|
||||||
return HTML{
|
return HTML{
|
||||||
Template: r.Template,
|
Template: r.Template,
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -56,7 +56,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
|
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
|
||||||
func (r HTMLDebug) Instance(name string, data interface{}) Render {
|
func (r HTMLDebug) Instance(name string, data any) Render {
|
||||||
return HTML{
|
return HTML{
|
||||||
Template: r.loadTemplate(),
|
Template: r.loadTemplate(),
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@ -16,39 +16,41 @@ import (
|
|||||||
|
|
||||||
// JSON contains the given interface object.
|
// JSON contains the given interface object.
|
||||||
type JSON struct {
|
type JSON struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndentedJSON contains the given interface object.
|
// IndentedJSON contains the given interface object.
|
||||||
type IndentedJSON struct {
|
type IndentedJSON struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecureJSON contains the given interface object and its prefix.
|
// SecureJSON contains the given interface object and its prefix.
|
||||||
type SecureJSON struct {
|
type SecureJSON struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonpJSON contains the given interface object its callback.
|
// JsonpJSON contains the given interface object its callback.
|
||||||
type JsonpJSON struct {
|
type JsonpJSON struct {
|
||||||
Callback string
|
Callback string
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsciiJSON contains the given interface object.
|
// AsciiJSON contains the given interface object.
|
||||||
type AsciiJSON struct {
|
type AsciiJSON struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// PureJSON contains the given interface object.
|
// PureJSON contains the given interface object.
|
||||||
type PureJSON struct {
|
type PureJSON struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
var (
|
||||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
var jsonAsciiContentType = []string{"application/json"}
|
jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||||
|
jsonASCIIContentType = []string{"application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
// Render (JSON) writes data with custom ContentType.
|
// Render (JSON) writes data with custom ContentType.
|
||||||
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||||
@ -64,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
func WriteJSON(w http.ResponseWriter, obj any) error {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := json.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,8 +102,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
|||||||
// if the jsonBytes is array values
|
// if the jsonBytes is array values
|
||||||
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
||||||
bytesconv.StringToBytes("]")) {
|
bytesconv.StringToBytes("]")) {
|
||||||
_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
|
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,20 +129,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callback := template.JSEscapeString(r.Callback)
|
callback := template.JSEscapeString(r.Callback)
|
||||||
_, err = w.Write(bytesconv.StringToBytes(callback))
|
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = w.Write(bytesconv.StringToBytes("("))
|
|
||||||
if err != nil {
|
if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = w.Write(ret)
|
|
||||||
if err != nil {
|
if _, err = w.Write(ret); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = w.Write(bytesconv.StringToBytes(");"))
|
|
||||||
if err != nil {
|
if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
|||||||
|
|
||||||
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonAsciiContentType)
|
writeContentType(w, jsonASCIIContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package render
|
package render
|
||||||
@ -12,13 +13,15 @@ import (
|
|||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check interface implemented here to support go build tag nomsgpack.
|
||||||
|
// See: https://github.com/gin-gonic/gin/pull/1852/
|
||||||
var (
|
var (
|
||||||
_ Render = MsgPack{}
|
_ Render = MsgPack{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// MsgPack contains the given interface object.
|
// MsgPack contains the given interface object.
|
||||||
type MsgPack struct {
|
type MsgPack struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
||||||
@ -34,7 +37,7 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
|
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
|
||||||
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
func WriteMsgPack(w http.ResponseWriter, obj any) error {
|
||||||
writeContentType(w, msgpackContentType)
|
writeContentType(w, msgpackContentType)
|
||||||
var mh codec.MsgpackHandle
|
var mh codec.MsgpackHandle
|
||||||
return codec.NewEncoder(w, &mh).Encode(obj)
|
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||||
|
|||||||
@ -7,12 +7,12 @@ package render
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtoBuf contains the given interface object.
|
// ProtoBuf contains the given interface object.
|
||||||
type ProtoBuf struct {
|
type ProtoBuf struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var protobufContentType = []string{"application/x-protobuf"}
|
var protobufContentType = []string{"application/x-protobuf"}
|
||||||
|
|||||||
@ -30,6 +30,7 @@ var (
|
|||||||
_ Render = Reader{}
|
_ Render = Reader{}
|
||||||
_ Render = AsciiJSON{}
|
_ Render = AsciiJSON{}
|
||||||
_ Render = ProtoBuf{}
|
_ Render = ProtoBuf{}
|
||||||
|
_ Render = TOML{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package render
|
package render
|
||||||
@ -20,7 +21,7 @@ import (
|
|||||||
|
|
||||||
func TestRenderMsgPack(t *testing.T) {
|
func TestRenderMsgPack(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +39,6 @@ func TestRenderMsgPack(t *testing.T) {
|
|||||||
err = codec.NewEncoder(buf, h).Encode(data)
|
err = codec.NewEncoder(buf, h).Encode(data)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), string(buf.Bytes()))
|
assert.Equal(t, w.Body.String(), buf.String())
|
||||||
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO unit tests
|
// TODO unit tests
|
||||||
@ -25,7 +24,7 @@ import (
|
|||||||
|
|
||||||
func TestRenderJSON(t *testing.T) {
|
func TestRenderJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"html": "<b>",
|
"html": "<b>",
|
||||||
}
|
}
|
||||||
@ -50,7 +49,7 @@ func TestRenderJSONPanics(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderIndentedJSON(t *testing.T) {
|
func TestRenderIndentedJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"bar": "foo",
|
"bar": "foo",
|
||||||
}
|
}
|
||||||
@ -73,7 +72,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderSecureJSON(t *testing.T) {
|
func TestRenderSecureJSON(t *testing.T) {
|
||||||
w1 := httptest.NewRecorder()
|
w1 := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +86,7 @@ func TestRenderSecureJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
w2 := httptest.NewRecorder()
|
w2 := httptest.NewRecorder()
|
||||||
datas := []map[string]interface{}{{
|
datas := []map[string]any{{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}, {
|
}, {
|
||||||
"bar": "foo",
|
"bar": "foo",
|
||||||
@ -110,7 +109,7 @@ func TestRenderSecureJSONFail(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderJsonpJSON(t *testing.T) {
|
func TestRenderJsonpJSON(t *testing.T) {
|
||||||
w1 := httptest.NewRecorder()
|
w1 := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +123,7 @@ func TestRenderJsonpJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
|
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
w2 := httptest.NewRecorder()
|
w2 := httptest.NewRecorder()
|
||||||
datas := []map[string]interface{}{{
|
datas := []map[string]any{{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}, {
|
}, {
|
||||||
"bar": "foo",
|
"bar": "foo",
|
||||||
@ -138,7 +137,7 @@ func TestRenderJsonpJSON(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderJsonpJSONError2(t *testing.T) {
|
func TestRenderJsonpJSONError2(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
(JsonpJSON{"", data}).WriteContentType(w)
|
(JsonpJSON{"", data}).WriteContentType(w)
|
||||||
@ -162,7 +161,7 @@ func TestRenderJsonpJSONFail(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderAsciiJSON(t *testing.T) {
|
func TestRenderAsciiJSON(t *testing.T) {
|
||||||
w1 := httptest.NewRecorder()
|
w1 := httptest.NewRecorder()
|
||||||
data1 := map[string]interface{}{
|
data1 := map[string]any{
|
||||||
"lang": "GO语言",
|
"lang": "GO语言",
|
||||||
"tag": "<br>",
|
"tag": "<br>",
|
||||||
}
|
}
|
||||||
@ -191,7 +190,7 @@ func TestRenderAsciiJSONFail(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderPureJSON(t *testing.T) {
|
func TestRenderPureJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"html": "<b>",
|
"html": "<b>",
|
||||||
}
|
}
|
||||||
@ -201,7 +200,7 @@ func TestRenderPureJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
type xmlmap map[string]interface{}
|
type xmlmap map[string]any
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
@ -245,7 +244,7 @@ b:
|
|||||||
type fail struct{}
|
type fail struct{}
|
||||||
|
|
||||||
// Hook MarshalYAML
|
// Hook MarshalYAML
|
||||||
func (ft *fail) MarshalYAML() (interface{}, error) {
|
func (ft *fail) MarshalYAML() (any, error) {
|
||||||
return nil, errors.New("fail")
|
return nil, errors.New("fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,13 +358,13 @@ func TestRenderString(t *testing.T) {
|
|||||||
|
|
||||||
(String{
|
(String{
|
||||||
Format: "hello %s %d",
|
Format: "hello %s %d",
|
||||||
Data: []interface{}{},
|
Data: []any{},
|
||||||
}).WriteContentType(w)
|
}).WriteContentType(w)
|
||||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (String{
|
err := (String{
|
||||||
Format: "hola %s %d",
|
Format: "hola %s %d",
|
||||||
Data: []interface{}{"manu", 2},
|
Data: []any{"manu", 2},
|
||||||
}).Render(w)
|
}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -378,7 +377,7 @@ func TestRenderStringLenZero(t *testing.T) {
|
|||||||
|
|
||||||
err := (String{
|
err := (String{
|
||||||
Format: "hola %s %d",
|
Format: "hola %s %d",
|
||||||
Data: []interface{}{},
|
Data: []any{},
|
||||||
}).Render(w)
|
}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -404,7 +403,7 @@ func TestRenderHTMLTemplate(t *testing.T) {
|
|||||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
|
|
||||||
htmlRender := HTMLProduction{Template: templ}
|
htmlRender := HTMLProduction{Template: templ}
|
||||||
instance := htmlRender.Instance("t", map[string]interface{}{
|
instance := htmlRender.Instance("t", map[string]any{
|
||||||
"name": "alexandernyquist",
|
"name": "alexandernyquist",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -420,7 +419,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
|||||||
templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
|
||||||
|
|
||||||
htmlRender := HTMLProduction{Template: templ}
|
htmlRender := HTMLProduction{Template: templ}
|
||||||
instance := htmlRender.Instance("", map[string]interface{}{
|
instance := htmlRender.Instance("", map[string]any{
|
||||||
"name": "alexandernyquist",
|
"name": "alexandernyquist",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -433,12 +432,13 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderHTMLDebugFiles(t *testing.T) {
|
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
|
htmlRender := HTMLDebug{
|
||||||
|
Files: []string{"../testdata/template/hello.tmpl"},
|
||||||
Glob: "",
|
Glob: "",
|
||||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||||
"name": "thinkerou",
|
"name": "thinkerou",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -451,12 +451,13 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
|
|||||||
|
|
||||||
func TestRenderHTMLDebugGlob(t *testing.T) {
|
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
htmlRender := HTMLDebug{Files: nil,
|
htmlRender := HTMLDebug{
|
||||||
|
Files: nil,
|
||||||
Glob: "../testdata/template/hello*",
|
Glob: "../testdata/template/hello*",
|
||||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||||
"name": "thinkerou",
|
"name": "thinkerou",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -468,7 +469,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderHTMLDebugPanics(t *testing.T) {
|
func TestRenderHTMLDebugPanics(t *testing.T) {
|
||||||
htmlRender := HTMLDebug{Files: nil,
|
htmlRender := HTMLDebug{
|
||||||
|
Files: nil,
|
||||||
Glob: "",
|
Glob: "",
|
||||||
Delims: Delims{"{{", "}}"},
|
Delims: Delims{"{{", "}}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
// String contains the given interface object slice and its format.
|
// String contains the given interface object slice and its format.
|
||||||
type String struct {
|
type String struct {
|
||||||
Format string
|
Format string
|
||||||
Data []interface{}
|
Data []any
|
||||||
}
|
}
|
||||||
|
|
||||||
var plainContentType = []string{"text/plain; charset=utf-8"}
|
var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||||
@ -30,7 +30,7 @@ func (r String) WriteContentType(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteString writes data according to its format and write custom ContentType.
|
// WriteString writes data according to its format and write custom ContentType.
|
||||||
func WriteString(w http.ResponseWriter, format string, data []interface{}, html bool) (err error) {
|
func WriteString(w http.ResponseWriter, format string, data []any, html bool) (err error) {
|
||||||
if html {
|
if html {
|
||||||
writeContentType(w, htmlContentType)
|
writeContentType(w, htmlContentType)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
36
render/toml.go
Normal file
36
render/toml.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TOML contains the given interface object.
|
||||||
|
type TOML struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var TOMLContentType = []string{"application/toml; charset=utf-8"}
|
||||||
|
|
||||||
|
// Render (TOML) marshals the given interface object and writes data with custom ContentType.
|
||||||
|
func (r TOML) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := toml.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (TOML) writes TOML ContentType for response.
|
||||||
|
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, TOMLContentType)
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// XML contains the given interface object.
|
// XML contains the given interface object.
|
||||||
type XML struct {
|
type XML struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// YAML contains the given interface object.
|
// YAML contains the given interface object.
|
||||||
type YAML struct {
|
type YAML struct {
|
||||||
Data interface{}
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||||
|
|||||||
@ -23,23 +23,23 @@ type ResponseWriter interface {
|
|||||||
http.Flusher
|
http.Flusher
|
||||||
http.CloseNotifier
|
http.CloseNotifier
|
||||||
|
|
||||||
// Returns the HTTP response status code of the current request.
|
// Status returns the HTTP response status code of the current request.
|
||||||
Status() int
|
Status() int
|
||||||
|
|
||||||
// Returns the number of bytes already written into the response http body.
|
// Size returns the number of bytes already written into the response http body.
|
||||||
// See Written()
|
// See Written()
|
||||||
Size() int
|
Size() int
|
||||||
|
|
||||||
// Writes the string into the response body.
|
// WriteString writes the string into the response body.
|
||||||
WriteString(string) (int, error)
|
WriteString(string) (int, error)
|
||||||
|
|
||||||
// Returns true if the response body was already written.
|
// Written returns true if the response body was already written.
|
||||||
Written() bool
|
Written() bool
|
||||||
|
|
||||||
// Forces to write the http header (status code + headers).
|
// WriteHeaderNow forces to write the http header (status code + headers).
|
||||||
WriteHeaderNow()
|
WriteHeaderNow()
|
||||||
|
|
||||||
// get the http.Pusher for server push
|
// Pusher get the http.Pusher for server push
|
||||||
Pusher() http.Pusher
|
Pusher() http.Pusher
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseNotify implements the http.CloseNotify interface.
|
// CloseNotify implements the http.CloseNotifier interface.
|
||||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush implements the http.Flush interface.
|
// Flush implements the http.Flusher interface.
|
||||||
func (w *responseWriter) Flush() {
|
func (w *responseWriter) Flush() {
|
||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
w.ResponseWriter.(http.Flusher).Flush()
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
|||||||
@ -17,12 +17,14 @@ import (
|
|||||||
// func (w *responseWriter) CloseNotify() <-chan bool {
|
// func (w *responseWriter) CloseNotify() <-chan bool {
|
||||||
// func (w *responseWriter) Flush() {
|
// func (w *responseWriter) Flush() {
|
||||||
|
|
||||||
var _ ResponseWriter = &responseWriter{}
|
var (
|
||||||
var _ http.ResponseWriter = &responseWriter{}
|
_ ResponseWriter = &responseWriter{}
|
||||||
var _ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
_ http.ResponseWriter = &responseWriter{}
|
||||||
var _ http.Hijacker = ResponseWriter(&responseWriter{})
|
_ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
||||||
var _ http.Flusher = ResponseWriter(&responseWriter{})
|
_ http.Hijacker = ResponseWriter(&responseWriter{})
|
||||||
var _ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
_ http.Flusher = ResponseWriter(&responseWriter{})
|
||||||
|
_ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
|
|||||||
@ -11,6 +11,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// regEnLetter matches english letters for http method name
|
||||||
|
regEnLetter = regexp.MustCompile("^[A-Z]+$")
|
||||||
|
|
||||||
|
// anyMethods for RouterGroup Any method
|
||||||
|
anyMethods = []string{
|
||||||
|
http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
|
||||||
|
http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
|
||||||
|
http.MethodTrace,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// IRouter defines all router handle interface includes single and group router.
|
// IRouter defines all router handle interface includes single and group router.
|
||||||
type IRouter interface {
|
type IRouter interface {
|
||||||
IRoutes
|
IRoutes
|
||||||
@ -32,6 +44,7 @@ type IRoutes interface {
|
|||||||
HEAD(string, ...HandlerFunc) IRoutes
|
HEAD(string, ...HandlerFunc) IRoutes
|
||||||
|
|
||||||
StaticFile(string, string) IRoutes
|
StaticFile(string, string) IRoutes
|
||||||
|
StaticFileFS(string, string, http.FileSystem) IRoutes
|
||||||
Static(string, string) IRoutes
|
Static(string, string) IRoutes
|
||||||
StaticFS(string, http.FileSystem) IRoutes
|
StaticFS(string, http.FileSystem) IRoutes
|
||||||
}
|
}
|
||||||
@ -87,7 +100,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
|
|||||||
// frequently used, non-standardized or custom methods (e.g. for internal
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
// communication with a proxy).
|
// communication with a proxy).
|
||||||
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
|
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
|
if matched := regEnLetter.MatchString(httpMethod); !matched {
|
||||||
panic("http method " + httpMethod + " is not valid")
|
panic("http method " + httpMethod + " is not valid")
|
||||||
}
|
}
|
||||||
return group.handle(httpMethod, relativePath, handlers)
|
return group.handle(httpMethod, relativePath, handlers)
|
||||||
@ -131,27 +144,34 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo
|
|||||||
// Any registers a route that matches all the HTTP methods.
|
// Any registers a route that matches all the HTTP methods.
|
||||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||||
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
group.handle(http.MethodGet, relativePath, handlers)
|
for _, method := range anyMethods {
|
||||||
group.handle(http.MethodPost, relativePath, handlers)
|
group.handle(method, relativePath, handlers)
|
||||||
group.handle(http.MethodPut, relativePath, handlers)
|
}
|
||||||
group.handle(http.MethodPatch, relativePath, handlers)
|
|
||||||
group.handle(http.MethodHead, relativePath, handlers)
|
|
||||||
group.handle(http.MethodOptions, relativePath, handlers)
|
|
||||||
group.handle(http.MethodDelete, relativePath, handlers)
|
|
||||||
group.handle(http.MethodConnect, relativePath, handlers)
|
|
||||||
group.handle(http.MethodTrace, relativePath, handlers)
|
|
||||||
return group.returnObj()
|
return group.returnObj()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticFile registers a single route in order to serve a single file of the local filesystem.
|
// StaticFile registers a single route in order to serve a single file of the local filesystem.
|
||||||
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
||||||
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
||||||
|
return group.staticFileHandler(relativePath, func(c *Context) {
|
||||||
|
c.File(filepath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
|
||||||
|
// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
|
||||||
|
// Gin by default user: gin.Dir()
|
||||||
|
func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
|
||||||
|
return group.staticFileHandler(relativePath, func(c *Context) {
|
||||||
|
c.FileFromFS(filepath, fs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
|
||||||
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
panic("URL parameters can not be used when serving a static file")
|
panic("URL parameters can not be used when serving a static file")
|
||||||
}
|
}
|
||||||
handler := func(c *Context) {
|
|
||||||
c.File(filepath)
|
|
||||||
}
|
|
||||||
group.GET(relativePath, handler)
|
group.GET(relativePath, handler)
|
||||||
group.HEAD(relativePath, handler)
|
group.HEAD(relativePath, handler)
|
||||||
return group.returnObj()
|
return group.returnObj()
|
||||||
@ -209,9 +229,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
|||||||
|
|
||||||
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||||
finalSize := len(group.Handlers) + len(handlers)
|
finalSize := len(group.Handlers) + len(handlers)
|
||||||
if finalSize >= int(abortIndex) {
|
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||||
panic("too many handlers")
|
|
||||||
}
|
|
||||||
mergedHandlers := make(HandlersChain, finalSize)
|
mergedHandlers := make(HandlersChain, finalSize)
|
||||||
copy(mergedHandlers, group.Handlers)
|
copy(mergedHandlers, group.Handlers)
|
||||||
copy(mergedHandlers[len(group.Handlers):], handlers)
|
copy(mergedHandlers[len(group.Handlers):], handlers)
|
||||||
|
|||||||
@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) {
|
|||||||
panic("unknown method")
|
panic("unknown method")
|
||||||
}
|
}
|
||||||
|
|
||||||
w := performRequest(router, method, "/v1/login/test")
|
w := PerformRequest(router, method, "/v1/login/test")
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
|
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, method, "/v1/test")
|
w = PerformRequest(router, method, "/v1/test")
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
||||||
}
|
}
|
||||||
@ -111,16 +111,31 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterGroupTooManyHandlers(t *testing.T) {
|
func TestRouterGroupInvalidStaticFileFS(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
handlers1 := make([]HandlerFunc, 40)
|
assert.Panics(t, func() {
|
||||||
|
router.StaticFileFS("/path/:param", "favicon.ico", Dir(".", false))
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
router.StaticFileFS("/path/*param", "favicon.ico", Dir(".", false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouterGroupTooManyHandlers(t *testing.T) {
|
||||||
|
const (
|
||||||
|
panicValue = "too many handlers"
|
||||||
|
maximumCnt = abortIndex
|
||||||
|
)
|
||||||
|
router := New()
|
||||||
|
handlers1 := make([]HandlerFunc, maximumCnt-1)
|
||||||
router.Use(handlers1...)
|
router.Use(handlers1...)
|
||||||
|
|
||||||
handlers2 := make([]HandlerFunc, 26)
|
handlers2 := make([]HandlerFunc, maximumCnt+1)
|
||||||
assert.Panics(t, func() {
|
assert.PanicsWithValue(t, panicValue, func() {
|
||||||
router.Use(handlers2...)
|
router.Use(handlers2...)
|
||||||
})
|
})
|
||||||
assert.Panics(t, func() {
|
assert.PanicsWithValue(t, panicValue, func() {
|
||||||
router.GET("/", handlers2...)
|
router.GET("/", handlers2...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -173,6 +188,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
|
|||||||
assert.Equal(t, r, r.HEAD("/", handler))
|
assert.Equal(t, r, r.HEAD("/", handler))
|
||||||
|
|
||||||
assert.Equal(t, r, r.StaticFile("/file", "."))
|
assert.Equal(t, r, r.StaticFile("/file", "."))
|
||||||
|
assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false)))
|
||||||
assert.Equal(t, r, r.Static("/static", "."))
|
assert.Equal(t, r, r.Static("/static", "."))
|
||||||
assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
|
assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
|
||||||
}
|
}
|
||||||
|
|||||||
162
routes_test.go
162
routes_test.go
@ -21,7 +21,8 @@ type header struct {
|
|||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
// PerformRequest for testing gin router.
|
||||||
|
func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
||||||
req := httptest.NewRequest(method, path, nil)
|
req := httptest.NewRequest(method, path, nil)
|
||||||
for _, h := range headers {
|
for _, h := range headers {
|
||||||
req.Header.Add(h.Key, h.Value)
|
req.Header.Add(h.Key, h.Value)
|
||||||
@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) {
|
|||||||
passed = true
|
passed = true
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(r, method, "/test")
|
w := PerformRequest(r, method, "/test")
|
||||||
assert.True(t, passed)
|
assert.True(t, passed)
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
performRequest(r, method, "/test2")
|
PerformRequest(r, method, "/test2")
|
||||||
assert.True(t, passedAny)
|
assert.True(t, passedAny)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) {
|
|||||||
passed = true
|
passed = true
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, method, "/test")
|
w := PerformRequest(router, method, "/test")
|
||||||
|
|
||||||
assert.False(t, passed)
|
assert.False(t, passed)
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) {
|
|||||||
passed = true
|
passed = true
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, method, "/test")
|
w := PerformRequest(router, method, "/test")
|
||||||
|
|
||||||
assert.False(t, passed)
|
assert.False(t, passed)
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) {
|
|||||||
c.String(http.StatusOK, "sup3")
|
c.String(http.StatusOK, "sup3")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodPut, "/hey")
|
w := PerformRequest(router, http.MethodPut, "/hey")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "called", w.Body.String())
|
assert.Equal(t, "called", w.Body.String())
|
||||||
@ -150,50 +151,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
|||||||
router.POST("/path3", func(c *Context) {})
|
router.POST("/path3", func(c *Context) {})
|
||||||
router.PUT("/path4/", func(c *Context) {})
|
router.PUT("/path4/", func(c *Context) {})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/path/")
|
w := PerformRequest(router, http.MethodGet, "/path/")
|
||||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path2")
|
w = PerformRequest(router, http.MethodGet, "/path2")
|
||||||
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPost, "/path3/")
|
w = PerformRequest(router, http.MethodPost, "/path3/")
|
||||||
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPut, "/path4")
|
w = PerformRequest(router, http.MethodPut, "/path4")
|
||||||
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path")
|
w = PerformRequest(router, http.MethodGet, "/path")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path2/")
|
w = PerformRequest(router, http.MethodGet, "/path2/")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPost, "/path3")
|
w = PerformRequest(router, http.MethodPost, "/path3")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPut, "/path4/")
|
w = PerformRequest(router, http.MethodPut, "/path4/")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
|
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
|
||||||
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
|
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, 301, w.Code)
|
assert.Equal(t, 301, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
router.RedirectTrailingSlash = false
|
router.RedirectTrailingSlash = false
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path/")
|
w = PerformRequest(router, http.MethodGet, "/path/")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, http.MethodGet, "/path2")
|
w = PerformRequest(router, http.MethodGet, "/path2")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, http.MethodPost, "/path3/")
|
w = PerformRequest(router, http.MethodPost, "/path3/")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, http.MethodPut, "/path4")
|
w = PerformRequest(router, http.MethodPut, "/path4")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,19 +208,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
|||||||
router.POST("/PATH3", func(c *Context) {})
|
router.POST("/PATH3", func(c *Context) {})
|
||||||
router.POST("/Path4/", func(c *Context) {})
|
router.POST("/Path4/", func(c *Context) {})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/PATH")
|
w := PerformRequest(router, http.MethodGet, "/PATH")
|
||||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodGet, "/path2")
|
w = PerformRequest(router, http.MethodGet, "/path2")
|
||||||
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPost, "/path3")
|
w = PerformRequest(router, http.MethodPost, "/path3")
|
||||||
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, http.MethodPost, "/path4")
|
w = PerformRequest(router, http.MethodPost, "/path4")
|
||||||
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||||
}
|
}
|
||||||
@ -238,7 +239,6 @@ func TestRouteParamsByName(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, name, c.Param("name"))
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, name, c.Param("name"))
|
|
||||||
assert.Equal(t, lastName, c.Param("last_name"))
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
assert.Empty(t, c.Param("wtf"))
|
assert.Empty(t, c.Param("wtf"))
|
||||||
@ -249,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) {
|
|||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "john", name)
|
assert.Equal(t, "john", name)
|
||||||
@ -272,7 +272,6 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, name, c.Param("name"))
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, name, c.Param("name"))
|
|
||||||
assert.Equal(t, lastName, c.Param("last_name"))
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
assert.Empty(t, c.Param("wtf"))
|
assert.Empty(t, c.Param("wtf"))
|
||||||
@ -283,7 +282,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
|||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
|
w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "john", name)
|
assert.Equal(t, "john", name)
|
||||||
@ -311,16 +310,50 @@ func TestRouteStaticFile(t *testing.T) {
|
|||||||
router.Static("/using_static", dir)
|
router.Static("/using_static", dir)
|
||||||
router.StaticFile("/result", f.Name())
|
router.StaticFile("/result", f.Name())
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/using_static/"+filename)
|
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
|
||||||
w2 := performRequest(router, http.MethodGet, "/result")
|
w2 := PerformRequest(router, http.MethodGet, "/result")
|
||||||
|
|
||||||
assert.Equal(t, w, w2)
|
assert.Equal(t, w, w2)
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
||||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
w3 := performRequest(router, http.MethodHead, "/using_static/"+filename)
|
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
|
||||||
w4 := performRequest(router, http.MethodHead, "/result")
|
w4 := PerformRequest(router, http.MethodHead, "/result")
|
||||||
|
|
||||||
|
assert.Equal(t, w3, w4)
|
||||||
|
assert.Equal(t, http.StatusOK, w3.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
|
func TestRouteStaticFileFS(t *testing.T) {
|
||||||
|
// SETUP file
|
||||||
|
testRoot, _ := os.Getwd()
|
||||||
|
f, err := ioutil.TempFile(testRoot, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
_, err = f.WriteString("Gin Web Framework")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
dir, filename := filepath.Split(f.Name())
|
||||||
|
// SETUP gin
|
||||||
|
router := New()
|
||||||
|
router.Static("/using_static", dir)
|
||||||
|
router.StaticFileFS("/result_fs", filename, Dir(dir, false))
|
||||||
|
|
||||||
|
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
|
||||||
|
w2 := PerformRequest(router, http.MethodGet, "/result_fs")
|
||||||
|
|
||||||
|
assert.Equal(t, w, w2)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
|
||||||
|
w4 := PerformRequest(router, http.MethodHead, "/result_fs")
|
||||||
|
|
||||||
assert.Equal(t, w3, w4)
|
assert.Equal(t, w3, w4)
|
||||||
assert.Equal(t, http.StatusOK, w3.Code)
|
assert.Equal(t, http.StatusOK, w3.Code)
|
||||||
@ -331,7 +364,7 @@ func TestRouteStaticListingDir(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.StaticFS("/", Dir("./", true))
|
router.StaticFS("/", Dir("./", true))
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "gin.go")
|
assert.Contains(t, w.Body.String(), "gin.go")
|
||||||
@ -343,7 +376,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Static("/", "./")
|
router.Static("/", "./")
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||||
@ -358,11 +391,13 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
|||||||
})
|
})
|
||||||
static.Static("/", "./")
|
static.Static("/", "./")
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/gin.go")
|
w := PerformRequest(router, http.MethodGet, "/gin.go")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "package gin")
|
assert.Contains(t, w.Body.String(), "package gin")
|
||||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
|
// else, Content-Type='text/x-go; charset=utf-8'
|
||||||
|
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
||||||
assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||||
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
|
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
|
||||||
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
|
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
|
||||||
@ -372,13 +407,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.HandleMethodNotAllowed = true
|
router.HandleMethodNotAllowed = true
|
||||||
router.POST("/path", func(c *Context) {})
|
router.POST("/path", func(c *Context) {})
|
||||||
w := performRequest(router, http.MethodGet, "/path")
|
w := PerformRequest(router, http.MethodGet, "/path")
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
|
|
||||||
router.NoMethod(func(c *Context) {
|
router.NoMethod(func(c *Context) {
|
||||||
c.String(http.StatusTeapot, "responseText")
|
c.String(http.StatusTeapot, "responseText")
|
||||||
})
|
})
|
||||||
w = performRequest(router, http.MethodGet, "/path")
|
w = PerformRequest(router, http.MethodGet, "/path")
|
||||||
assert.Equal(t, "responseText", w.Body.String())
|
assert.Equal(t, "responseText", w.Body.String())
|
||||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||||
}
|
}
|
||||||
@ -389,7 +424,7 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
|
|||||||
// add one methodTree to trees
|
// add one methodTree to trees
|
||||||
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
|
||||||
router.GET("/path2", func(c *Context) {})
|
router.GET("/path2", func(c *Context) {})
|
||||||
w := performRequest(router, http.MethodPost, "/path2")
|
w := PerformRequest(router, http.MethodPost, "/path2")
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,13 +432,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.HandleMethodNotAllowed = false
|
router.HandleMethodNotAllowed = false
|
||||||
router.POST("/path", func(c *Context) {})
|
router.POST("/path", func(c *Context) {})
|
||||||
w := performRequest(router, http.MethodGet, "/path")
|
w := PerformRequest(router, http.MethodGet, "/path")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
|
||||||
router.NoMethod(func(c *Context) {
|
router.NoMethod(func(c *Context) {
|
||||||
c.String(http.StatusTeapot, "responseText")
|
c.String(http.StatusTeapot, "responseText")
|
||||||
})
|
})
|
||||||
w = performRequest(router, http.MethodGet, "/path")
|
w = PerformRequest(router, http.MethodGet, "/path")
|
||||||
assert.Equal(t, "404 page not found", w.Body.String())
|
assert.Equal(t, "404 page not found", w.Body.String())
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
@ -423,7 +458,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
|
|||||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
w := performRequest(router, "GET", tr.route)
|
w := PerformRequest(router, "GET", tr.route)
|
||||||
assert.Equal(t, tr.code, w.Code)
|
assert.Equal(t, tr.code, w.Code)
|
||||||
if w.Code != http.StatusNotFound {
|
if w.Code != http.StatusNotFound {
|
||||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||||
@ -453,7 +488,7 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
w := performRequest(router, http.MethodGet, tr.route)
|
w := PerformRequest(router, http.MethodGet, tr.route)
|
||||||
assert.Equal(t, tr.code, w.Code)
|
assert.Equal(t, tr.code, w.Code)
|
||||||
if w.Code != http.StatusNotFound {
|
if w.Code != http.StatusNotFound {
|
||||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||||
@ -466,21 +501,36 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
c.AbortWithStatus(http.StatusNotFound)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
notFound = true
|
notFound = true
|
||||||
})
|
})
|
||||||
w := performRequest(router, http.MethodGet, "/nope")
|
w := PerformRequest(router, http.MethodGet, "/nope")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
assert.True(t, notFound)
|
assert.True(t, notFound)
|
||||||
|
|
||||||
// Test other method than GET (want 307 instead of 301)
|
// Test other method than GET (want 307 instead of 301)
|
||||||
router.PATCH("/path", func(c *Context) {})
|
router.PATCH("/path", func(c *Context) {})
|
||||||
w = performRequest(router, http.MethodPatch, "/path/")
|
w = PerformRequest(router, http.MethodPatch, "/path/")
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||||
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
||||||
|
|
||||||
// Test special case where no node for the prefix "/" exists
|
// Test special case where no node for the prefix "/" exists
|
||||||
router = New()
|
router = New()
|
||||||
router.GET("/a", func(c *Context) {})
|
router.GET("/a", func(c *Context) {})
|
||||||
w = performRequest(router, http.MethodGet, "/")
|
w = PerformRequest(router, http.MethodGet, "/")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
|
||||||
|
// Reproduction test for the bug of issue #2843
|
||||||
|
router = New()
|
||||||
|
router.NoRoute(func(c *Context) {
|
||||||
|
if c.Request.RequestURI == "/login" {
|
||||||
|
c.String(200, "login")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.GET("/logout", func(c *Context) {
|
||||||
|
c.String(200, "logout")
|
||||||
|
})
|
||||||
|
w = PerformRequest(router, http.MethodGet, "/login")
|
||||||
|
assert.Equal(t, "login", w.Body.String())
|
||||||
|
w = PerformRequest(router, http.MethodGet, "/logout")
|
||||||
|
assert.Equal(t, "logout", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterStaticFSNotFound(t *testing.T) {
|
func TestRouterStaticFSNotFound(t *testing.T) {
|
||||||
@ -490,10 +540,10 @@ func TestRouterStaticFSNotFound(t *testing.T) {
|
|||||||
c.String(404, "non existent")
|
c.String(404, "non existent")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/nonexistent")
|
w := PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||||
assert.Equal(t, "non existent", w.Body.String())
|
assert.Equal(t, "non existent", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, http.MethodHead, "/nonexistent")
|
w = PerformRequest(router, http.MethodHead, "/nonexistent")
|
||||||
assert.Equal(t, "non existent", w.Body.String())
|
assert.Equal(t, "non existent", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,7 +553,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
|
|||||||
router.StaticFS("/", http.FileSystem(http.Dir(".")))
|
router.StaticFS("/", http.FileSystem(http.Dir(".")))
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
performRequest(router, http.MethodGet, "/nonexistent")
|
PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,11 +570,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
|
|||||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||||
|
|
||||||
// First access
|
// First access
|
||||||
performRequest(router, http.MethodGet, "/nonexistent")
|
PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||||
assert.Equal(t, 1, middlewareCalledNum)
|
assert.Equal(t, 1, middlewareCalledNum)
|
||||||
|
|
||||||
// Second access
|
// Second access
|
||||||
performRequest(router, http.MethodHead, "/nonexistent")
|
PerformRequest(router, http.MethodHead, "/nonexistent")
|
||||||
assert.Equal(t, 2, middlewareCalledNum)
|
assert.Equal(t, 2, middlewareCalledNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +593,7 @@ func TestRouteRawPath(t *testing.T) {
|
|||||||
assert.Equal(t, "222", num)
|
assert.Equal(t, "222", num)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
|
w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,7 +613,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
|||||||
assert.Equal(t, "333", num)
|
assert.Equal(t, "333", num)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
|
w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,7 +624,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, http.MethodGet, "/NotFound")
|
w := PerformRequest(route, http.MethodGet, "/NotFound")
|
||||||
assert.Equal(t, 421, w.Code)
|
assert.Equal(t, 421, w.Code)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
@ -608,7 +658,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
w := performRequest(router, http.MethodGet, route)
|
w := PerformRequest(router, http.MethodGet, route)
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,6 +668,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
|||||||
assert.Equal(t, "", c.FullPath())
|
assert.Equal(t, "", c.FullPath())
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, http.MethodGet, "/not-found")
|
w := PerformRequest(router, http.MethodGet, "/not-found")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|||||||
10
testdata/protoexample/any.go
vendored
Normal file
10
testdata/protoexample/any.go
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2021 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package protoexample
|
||||||
|
|
||||||
|
type any = interface{}
|
||||||
281
testdata/protoexample/test.pb.go
vendored
281
testdata/protoexample/test.pb.go
vendored
@ -1,24 +1,24 @@
|
|||||||
// Code generated by protoc-gen-go.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.0
|
||||||
|
// protoc v3.15.8
|
||||||
// source: test.proto
|
// source: test.proto
|
||||||
// DO NOT EDIT!
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package protoexample is a generated protocol buffer package.
|
|
||||||
|
|
||||||
It is generated from these files:
|
|
||||||
test.proto
|
|
||||||
|
|
||||||
It has these top-level messages:
|
|
||||||
Test
|
|
||||||
*/
|
|
||||||
package protoexample
|
package protoexample
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
import (
|
||||||
import math "math"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
const (
|
||||||
var _ = proto.Marshal
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
var _ = math.Inf
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
type FOO int32
|
type FOO int32
|
||||||
|
|
||||||
@ -26,88 +26,273 @@ const (
|
|||||||
FOO_X FOO = 17
|
FOO_X FOO = 17
|
||||||
)
|
)
|
||||||
|
|
||||||
var FOO_name = map[int32]string{
|
// Enum value maps for FOO.
|
||||||
|
var (
|
||||||
|
FOO_name = map[int32]string{
|
||||||
17: "X",
|
17: "X",
|
||||||
}
|
}
|
||||||
var FOO_value = map[string]int32{
|
FOO_value = map[string]int32{
|
||||||
"X": 17,
|
"X": 17,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func (x FOO) Enum() *FOO {
|
func (x FOO) Enum() *FOO {
|
||||||
p := new(FOO)
|
p := new(FOO)
|
||||||
*p = x
|
*p = x
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x FOO) String() string {
|
func (x FOO) String() string {
|
||||||
return proto.EnumName(FOO_name, int32(x))
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
}
|
}
|
||||||
func (x *FOO) UnmarshalJSON(data []byte) error {
|
|
||||||
value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO")
|
func (FOO) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_test_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FOO) Type() protoreflect.EnumType {
|
||||||
|
return &file_test_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x FOO) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func (x *FOO) UnmarshalJSON(b []byte) error {
|
||||||
|
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*x = FOO(value)
|
*x = FOO(num)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FOO.Descriptor instead.
|
||||||
|
func (FOO) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_test_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
type Test struct {
|
type Test struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
}
|
||||||
|
|
||||||
|
// Default values for Test fields.
|
||||||
|
const (
|
||||||
|
Default_Test_Type = int32(77)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x *Test) Reset() {
|
||||||
|
*x = Test{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_test_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Test) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Test) Reset() { *m = Test{} }
|
|
||||||
func (m *Test) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Test) ProtoMessage() {}
|
func (*Test) ProtoMessage() {}
|
||||||
|
|
||||||
const Default_Test_Type int32 = 77
|
func (x *Test) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_test_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Test) GetLabel() string {
|
// Deprecated: Use Test.ProtoReflect.Descriptor instead.
|
||||||
if m != nil && m.Label != nil {
|
func (*Test) Descriptor() ([]byte, []int) {
|
||||||
return *m.Label
|
return file_test_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Test) GetLabel() string {
|
||||||
|
if x != nil && x.Label != nil {
|
||||||
|
return *x.Label
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Test) GetType() int32 {
|
func (x *Test) GetType() int32 {
|
||||||
if m != nil && m.Type != nil {
|
if x != nil && x.Type != nil {
|
||||||
return *m.Type
|
return *x.Type
|
||||||
}
|
}
|
||||||
return Default_Test_Type
|
return Default_Test_Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Test) GetReps() []int64 {
|
func (x *Test) GetReps() []int64 {
|
||||||
if m != nil {
|
if x != nil {
|
||||||
return m.Reps
|
return x.Reps
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
func (x *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||||
if m != nil {
|
if x != nil {
|
||||||
return m.Optionalgroup
|
return x.Optionalgroup
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Test_OptionalGroup struct {
|
type Test_OptionalGroup struct {
|
||||||
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
state protoimpl.MessageState
|
||||||
XXX_unrecognized []byte `json:"-"`
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
RequiredField *string `protobuf:"bytes,5,req,name=RequiredField" json:"RequiredField,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Test_OptionalGroup) Reset() {
|
||||||
|
*x = Test_OptionalGroup{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_test_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Test_OptionalGroup) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
|
||||||
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Test_OptionalGroup) ProtoMessage() {}
|
func (*Test_OptionalGroup) ProtoMessage() {}
|
||||||
|
|
||||||
func (m *Test_OptionalGroup) GetRequiredField() string {
|
func (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {
|
||||||
if m != nil && m.RequiredField != nil {
|
mi := &file_test_proto_msgTypes[1]
|
||||||
return *m.RequiredField
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Test_OptionalGroup) Descriptor() ([]byte, []int) {
|
||||||
|
return file_test_proto_rawDescGZIP(), []int{0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Test_OptionalGroup) GetRequiredField() string {
|
||||||
|
if x != nil && x.RequiredField != nil {
|
||||||
|
return *x.RequiredField
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
var File_test_proto protoreflect.FileDescriptor
|
||||||
proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
|
|
||||||
|
var file_test_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,
|
||||||
|
0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,
|
||||||
|
0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,
|
||||||
|
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||||
|
0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
|
||||||
|
0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,
|
||||||
|
0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,
|
||||||
|
0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,
|
||||||
|
0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,
|
||||||
|
0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,
|
||||||
|
0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,
|
||||||
|
0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,
|
||||||
|
0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58,
|
||||||
|
0x10, 0x11,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_test_proto_rawDescOnce sync.Once
|
||||||
|
file_test_proto_rawDescData = file_test_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_test_proto_rawDescGZIP() []byte {
|
||||||
|
file_test_proto_rawDescOnce.Do(func() {
|
||||||
|
file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_test_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_test_proto_goTypes = []any{
|
||||||
|
(FOO)(0), // 0: protoexample.FOO
|
||||||
|
(*Test)(nil), // 1: protoexample.Test
|
||||||
|
(*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup
|
||||||
|
}
|
||||||
|
var file_test_proto_depIdxs = []int32{
|
||||||
|
2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_test_proto_init() }
|
||||||
|
func file_test_proto_init() {
|
||||||
|
if File_test_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_test_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*Test); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_test_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*Test_OptionalGroup); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_test_proto_rawDesc,
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_test_proto_goTypes,
|
||||||
|
DependencyIndexes: file_test_proto_depIdxs,
|
||||||
|
EnumInfos: file_test_proto_enumTypes,
|
||||||
|
MessageInfos: file_test_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_test_proto = out.File
|
||||||
|
file_test_proto_rawDesc = nil
|
||||||
|
file_test_proto_goTypes = nil
|
||||||
|
file_test_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
274
tree.go
274
tree.go
@ -17,6 +17,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
strColon = []byte(":")
|
strColon = []byte(":")
|
||||||
strStar = []byte("*")
|
strStar = []byte("*")
|
||||||
|
strSlash = []byte("/")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Param is a single URL parameter, consisting of a key and a value.
|
// Param is a single URL parameter, consisting of a key and a value.
|
||||||
@ -30,8 +31,8 @@ type Param struct {
|
|||||||
// It is therefore safe to read values by the index.
|
// It is therefore safe to read values by the index.
|
||||||
type Params []Param
|
type Params []Param
|
||||||
|
|
||||||
// Get returns the value of the first Param which key matches the given name.
|
// Get returns the value of the first Param which key matches the given name and a boolean true.
|
||||||
// If no matching Param is found, an empty string is returned.
|
// If no matching Param is found, an empty string is returned and a boolean false .
|
||||||
func (ps Params) Get(name string) (string, bool) {
|
func (ps Params) Get(name string) (string, bool) {
|
||||||
for _, entry := range ps {
|
for _, entry := range ps {
|
||||||
if entry.Key == name {
|
if entry.Key == name {
|
||||||
@ -80,6 +81,16 @@ func longestCommonPrefix(a, b string) int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addChild will add a child node, keeping wildcardChild at the end
|
||||||
|
func (n *node) addChild(child *node) {
|
||||||
|
if n.wildChild && len(n.children) > 0 {
|
||||||
|
wildcardChild := n.children[len(n.children)-1]
|
||||||
|
n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
|
||||||
|
} else {
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func countParams(path string) uint16 {
|
func countParams(path string) uint16 {
|
||||||
var n uint16
|
var n uint16
|
||||||
s := bytesconv.StringToBytes(path)
|
s := bytesconv.StringToBytes(path)
|
||||||
@ -88,11 +99,15 @@ func countParams(path string) uint16 {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countSections(path string) uint16 {
|
||||||
|
s := bytesconv.StringToBytes(path)
|
||||||
|
return uint16(bytes.Count(s, strSlash))
|
||||||
|
}
|
||||||
|
|
||||||
type nodeType uint8
|
type nodeType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
static nodeType = iota // default
|
root nodeType = iota + 1
|
||||||
root
|
|
||||||
param
|
param
|
||||||
catchAll
|
catchAll
|
||||||
)
|
)
|
||||||
@ -103,7 +118,7 @@ type node struct {
|
|||||||
wildChild bool
|
wildChild bool
|
||||||
nType nodeType
|
nType nodeType
|
||||||
priority uint32
|
priority uint32
|
||||||
children []*node
|
children []*node // child nodes, at most 1 :param style node at the end of the array
|
||||||
handlers HandlersChain
|
handlers HandlersChain
|
||||||
fullPath string
|
fullPath string
|
||||||
}
|
}
|
||||||
@ -119,7 +134,6 @@ func (n *node) incrementChildPrio(pos int) int {
|
|||||||
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
||||||
// Swap node positions
|
// Swap node positions
|
||||||
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build new index char string
|
// Build new index char string
|
||||||
@ -178,36 +192,9 @@ walk:
|
|||||||
// Make new node a child of this node
|
// Make new node a child of this node
|
||||||
if i < len(path) {
|
if i < len(path) {
|
||||||
path = path[i:]
|
path = path[i:]
|
||||||
|
|
||||||
if n.wildChild {
|
|
||||||
parentFullPathIndex += len(n.path)
|
|
||||||
n = n.children[0]
|
|
||||||
n.priority++
|
|
||||||
|
|
||||||
// Check if the wildcard matches
|
|
||||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
|
||||||
// Adding a child to a catchAll is not possible
|
|
||||||
n.nType != catchAll &&
|
|
||||||
// Check for longer wildcard, e.g. :name and :names
|
|
||||||
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
|
|
||||||
pathSeg := path
|
|
||||||
if n.nType != catchAll {
|
|
||||||
pathSeg = strings.SplitN(path, "/", 2)[0]
|
|
||||||
}
|
|
||||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
|
||||||
panic("'" + pathSeg +
|
|
||||||
"' in new path '" + fullPath +
|
|
||||||
"' conflicts with existing wildcard '" + n.path +
|
|
||||||
"' in existing prefix '" + prefix +
|
|
||||||
"'")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := path[0]
|
c := path[0]
|
||||||
|
|
||||||
// slash after param
|
// '/' after param
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
parentFullPathIndex += len(n.path)
|
parentFullPathIndex += len(n.path)
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
@ -226,21 +213,47 @@ walk:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise insert it
|
// Otherwise insert it
|
||||||
if c != ':' && c != '*' {
|
if c != ':' && c != '*' && n.nType != catchAll {
|
||||||
// []byte for proper unicode char conversion, see #65
|
// []byte for proper unicode char conversion, see #65
|
||||||
n.indices += bytesconv.BytesToString([]byte{c})
|
n.indices += bytesconv.BytesToString([]byte{c})
|
||||||
child := &node{
|
child := &node{
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = append(n.children, child)
|
n.addChild(child)
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
n = child
|
n = child
|
||||||
|
} else if n.wildChild {
|
||||||
|
// inserting a wildcard node, need to check if it conflicts with the existing wildcard
|
||||||
|
n = n.children[len(n.children)-1]
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// Check if the wildcard matches
|
||||||
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||||
|
// Adding a child to a catchAll is not possible
|
||||||
|
n.nType != catchAll &&
|
||||||
|
// Check for longer wildcard, e.g. :name and :names
|
||||||
|
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||||
|
continue walk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wildcard conflict
|
||||||
|
pathSeg := path
|
||||||
|
if n.nType != catchAll {
|
||||||
|
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
|
||||||
|
}
|
||||||
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
|
panic("'" + pathSeg +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in existing prefix '" + prefix +
|
||||||
|
"'")
|
||||||
|
}
|
||||||
|
|
||||||
n.insertChild(path, fullPath, handlers)
|
n.insertChild(path, fullPath, handlers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise and handle to current node
|
// Otherwise add handle to current node
|
||||||
if n.handlers != nil {
|
if n.handlers != nil {
|
||||||
panic("handlers are already registered for path '" + fullPath + "'")
|
panic("handlers are already registered for path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
@ -283,7 +296,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// The wildcard name must not contain ':' and '*'
|
// The wildcard name must only contain one ':' or '*' character
|
||||||
if !valid {
|
if !valid {
|
||||||
panic("only one wildcard per path segment is allowed, has: '" +
|
panic("only one wildcard per path segment is allowed, has: '" +
|
||||||
wildcard + "' in path '" + fullPath + "'")
|
wildcard + "' in path '" + fullPath + "'")
|
||||||
@ -294,13 +307,6 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this node has existing children which would be
|
|
||||||
// unreachable if we insert the wildcard here
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
panic("wildcard segment '" + wildcard +
|
|
||||||
"' conflicts with existing children in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if wildcard[0] == ':' { // param
|
if wildcard[0] == ':' { // param
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
// Insert prefix before the current wildcard
|
// Insert prefix before the current wildcard
|
||||||
@ -308,18 +314,18 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
path = path[i:]
|
path = path[i:]
|
||||||
}
|
}
|
||||||
|
|
||||||
n.wildChild = true
|
|
||||||
child := &node{
|
child := &node{
|
||||||
nType: param,
|
nType: param,
|
||||||
path: wildcard,
|
path: wildcard,
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.addChild(child)
|
||||||
|
n.wildChild = true
|
||||||
n = child
|
n = child
|
||||||
n.priority++
|
n.priority++
|
||||||
|
|
||||||
// if the path doesn't end with the wildcard, then there
|
// if the path doesn't end with the wildcard, then there
|
||||||
// will be another non-wildcard subpath starting with '/'
|
// will be another subpath starting with '/'
|
||||||
if len(wildcard) < len(path) {
|
if len(wildcard) < len(path) {
|
||||||
path = path[len(wildcard):]
|
path = path[len(wildcard):]
|
||||||
|
|
||||||
@ -327,7 +333,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.addChild(child)
|
||||||
n = child
|
n = child
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -343,7 +349,12 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
|
||||||
|
panic("catch-all wildcard '" + path +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
|
"' conflicts with existing path segment '" + pathSeg +
|
||||||
|
"' in existing prefix '" + n.path + pathSeg +
|
||||||
|
"'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently fixed width 1 for '/'
|
// currently fixed width 1 for '/'
|
||||||
@ -361,7 +372,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
n.children = []*node{child}
|
n.addChild(child)
|
||||||
n.indices = string('/')
|
n.indices = string('/')
|
||||||
n = child
|
n = child
|
||||||
n.priority++
|
n.priority++
|
||||||
@ -393,41 +404,90 @@ type nodeValue struct {
|
|||||||
fullPath string
|
fullPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type skippedNode struct {
|
||||||
|
path string
|
||||||
|
node *node
|
||||||
|
paramsCount int16
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the handle registered with the given path (key). The values of
|
// Returns the handle registered with the given path (key). The values of
|
||||||
// wildcards are saved to a map.
|
// wildcards are saved to a map.
|
||||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||||
// made if a handle exists with an extra (without the) trailing slash for the
|
// made if a handle exists with an extra (without the) trailing slash for the
|
||||||
// given path.
|
// given path.
|
||||||
func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
|
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
||||||
|
var globalParamsCount int16
|
||||||
|
|
||||||
walk: // Outer loop for walking the tree
|
walk: // Outer loop for walking the tree
|
||||||
for {
|
for {
|
||||||
prefix := n.path
|
prefix := n.path
|
||||||
if len(path) > len(prefix) {
|
if len(path) > len(prefix) {
|
||||||
if path[:len(prefix)] == prefix {
|
if path[:len(prefix)] == prefix {
|
||||||
path = path[len(prefix):]
|
path = path[len(prefix):]
|
||||||
// If this node does not have a wildcard (param or catchAll)
|
|
||||||
// child, we can just look up the next child node and continue
|
// Try all the non-wildcard children first by matching the indices
|
||||||
// to walk down the tree
|
|
||||||
if !n.wildChild {
|
|
||||||
idxc := path[0]
|
idxc := path[0]
|
||||||
for i, c := range []byte(n.indices) {
|
for i, c := range []byte(n.indices) {
|
||||||
if c == idxc {
|
if c == idxc {
|
||||||
|
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
|
||||||
|
if n.wildChild {
|
||||||
|
index := len(*skippedNodes)
|
||||||
|
*skippedNodes = (*skippedNodes)[:index+1]
|
||||||
|
(*skippedNodes)[index] = skippedNode{
|
||||||
|
path: prefix + path,
|
||||||
|
node: &node{
|
||||||
|
path: n.path,
|
||||||
|
wildChild: n.wildChild,
|
||||||
|
nType: n.nType,
|
||||||
|
priority: n.priority,
|
||||||
|
children: n.children,
|
||||||
|
handlers: n.handlers,
|
||||||
|
fullPath: n.fullPath,
|
||||||
|
},
|
||||||
|
paramsCount: globalParamsCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
continue walk
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !n.wildChild {
|
||||||
|
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||||
|
// the current node needs to roll back to last valid skippedNode
|
||||||
|
if path != "/" {
|
||||||
|
for l := len(*skippedNodes); l > 0; {
|
||||||
|
skippedNode := (*skippedNodes)[l-1]
|
||||||
|
*skippedNodes = (*skippedNodes)[:l-1]
|
||||||
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
|
path = skippedNode.path
|
||||||
|
n = skippedNode.node
|
||||||
|
if value.params != nil {
|
||||||
|
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||||
|
}
|
||||||
|
globalParamsCount = skippedNode.paramsCount
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Nothing found.
|
// Nothing found.
|
||||||
// We can recommend to redirect to the same URL without a
|
// We can recommend to redirect to the same URL without a
|
||||||
// trailing slash if a leaf exists for that path.
|
// trailing slash if a leaf exists for that path.
|
||||||
value.tsr = (path == "/" && n.handlers != nil)
|
value.tsr = path == "/" && n.handlers != nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle wildcard child
|
// Handle wildcard child, which is always at the end of the array
|
||||||
n = n.children[0]
|
n = n.children[len(n.children)-1]
|
||||||
|
globalParamsCount++
|
||||||
|
|
||||||
switch n.nType {
|
switch n.nType {
|
||||||
case param:
|
case param:
|
||||||
|
// fix truncate the parameter
|
||||||
|
// tree_test.go line: 204
|
||||||
|
|
||||||
// Find param end (either '/' or path end)
|
// Find param end (either '/' or path end)
|
||||||
end := 0
|
end := 0
|
||||||
for end < len(path) && path[end] != '/' {
|
for end < len(path) && path[end] != '/' {
|
||||||
@ -435,7 +495,7 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil {
|
if params != nil && cap(*params) > 0 {
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
value.params = params
|
value.params = params
|
||||||
}
|
}
|
||||||
@ -463,7 +523,7 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... but we can't
|
// ... but we can't
|
||||||
value.tsr = (len(path) == end+1)
|
value.tsr = len(path) == end+1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +535,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
// trailing slash exists for TSR recommendation
|
// trailing slash exists for TSR recommendation
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
value.tsr = (n.path == "/" && n.handlers != nil)
|
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -511,6 +571,24 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
if path == prefix {
|
if path == prefix {
|
||||||
|
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
|
||||||
|
// the current node needs to roll back to last valid skippedNode
|
||||||
|
if n.handlers == nil && path != "/" {
|
||||||
|
for l := len(*skippedNodes); l > 0; {
|
||||||
|
skippedNode := (*skippedNodes)[l-1]
|
||||||
|
*skippedNodes = (*skippedNodes)[:l-1]
|
||||||
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
|
path = skippedNode.path
|
||||||
|
n = skippedNode.node
|
||||||
|
if value.params != nil {
|
||||||
|
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||||
|
}
|
||||||
|
globalParamsCount = skippedNode.paramsCount
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// n = latestNode.children[len(latestNode.children)-1]
|
||||||
|
}
|
||||||
// We should have reached the node containing the handle.
|
// We should have reached the node containing the handle.
|
||||||
// Check if this node has a handle registered.
|
// Check if this node has a handle registered.
|
||||||
if value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
@ -542,9 +620,27 @@ walk: // Outer loop for walking the tree
|
|||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
// extra trailing slash if a leaf exists for that path
|
// extra trailing slash if a leaf exists for that path
|
||||||
value.tsr = (path == "/") ||
|
value.tsr = path == "/" ||
|
||||||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
||||||
|
|
||||||
|
// roll back to last valid skippedNode
|
||||||
|
if !value.tsr && path != "/" {
|
||||||
|
for l := len(*skippedNodes); l > 0; {
|
||||||
|
skippedNode := (*skippedNodes)[l-1]
|
||||||
|
*skippedNodes = (*skippedNodes)[:l-1]
|
||||||
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
|
path = skippedNode.path
|
||||||
|
n = skippedNode.node
|
||||||
|
if value.params != nil {
|
||||||
|
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||||
|
}
|
||||||
|
globalParamsCount = skippedNode.paramsCount
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -559,8 +655,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]by
|
|||||||
// Use a static sized buffer on the stack in the common case.
|
// Use a static sized buffer on the stack in the common case.
|
||||||
// If the path is too long, allocate a buffer on the heap instead.
|
// If the path is too long, allocate a buffer on the heap instead.
|
||||||
buf := make([]byte, 0, stackBufSize)
|
buf := make([]byte, 0, stackBufSize)
|
||||||
if l := len(path) + 1; l > stackBufSize {
|
if length := len(path) + 1; length > stackBufSize {
|
||||||
buf = make([]byte, 0, l)
|
buf = make([]byte, 0, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
ciPath := n.findCaseInsensitivePathRec(
|
ciPath := n.findCaseInsensitivePathRec(
|
||||||
@ -600,7 +696,30 @@ walk: // Outer loop for walking the tree
|
|||||||
path = path[npLen:]
|
path = path[npLen:]
|
||||||
ciPath = append(ciPath, n.path...)
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
if len(path) > 0 {
|
if len(path) == 0 {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if n.handlers != nil {
|
||||||
|
return ciPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found.
|
||||||
|
// Try to fix the path by adding a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
for i, c := range []byte(n.indices) {
|
||||||
|
if c == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
if (len(n.path) == 1 && n.handlers != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handlers != nil) {
|
||||||
|
return append(ciPath, '/')
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// If this node does not have a wildcard (param or catchAll) child,
|
// If this node does not have a wildcard (param or catchAll) child,
|
||||||
// we can just look up the next child node and continue to walk down
|
// we can just look up the next child node and continue to walk down
|
||||||
// the tree
|
// the tree
|
||||||
@ -735,29 +854,6 @@ walk: // Outer loop for walking the tree
|
|||||||
default:
|
default:
|
||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// We should have reached the node containing the handle.
|
|
||||||
// Check if this node has a handle registered.
|
|
||||||
if n.handlers != nil {
|
|
||||||
return ciPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// No handle found.
|
|
||||||
// Try to fix the path by adding a trailing slash
|
|
||||||
if fixTrailingSlash {
|
|
||||||
for i, c := range []byte(n.indices) {
|
|
||||||
if c == '/' {
|
|
||||||
n = n.children[i]
|
|
||||||
if (len(n.path) == 1 && n.handlers != nil) ||
|
|
||||||
(n.nType == catchAll && n.children[0].handlers != nil) {
|
|
||||||
return append(ciPath, '/')
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found.
|
// Nothing found.
|
||||||
|
|||||||
249
tree_test.go
249
tree_test.go
@ -33,6 +33,11 @@ func getParams() *Params {
|
|||||||
return &ps
|
return &ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSkippedNodes() *[]skippedNode {
|
||||||
|
ps := make([]skippedNode, 0, 20)
|
||||||
|
return &ps
|
||||||
|
}
|
||||||
|
|
||||||
func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
|
func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
|
||||||
unescape := false
|
unescape := false
|
||||||
if len(unescapes) >= 1 {
|
if len(unescapes) >= 1 {
|
||||||
@ -40,7 +45,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ..
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, request := range requests {
|
for _, request := range requests {
|
||||||
value := tree.getValue(request.path, getParams(), unescape)
|
value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)
|
||||||
|
|
||||||
if value.handlers == nil {
|
if value.handlers == nil {
|
||||||
if !request.nilHandler {
|
if !request.nilHandler {
|
||||||
@ -135,11 +140,16 @@ func TestTreeWildcard(t *testing.T) {
|
|||||||
|
|
||||||
routes := [...]string{
|
routes := [...]string{
|
||||||
"/",
|
"/",
|
||||||
"/cmd/:tool/:sub",
|
|
||||||
"/cmd/:tool/",
|
"/cmd/:tool/",
|
||||||
|
"/cmd/:tool/:sub",
|
||||||
|
"/cmd/whoami",
|
||||||
|
"/cmd/whoami/root",
|
||||||
|
"/cmd/whoami/root/",
|
||||||
"/src/*filepath",
|
"/src/*filepath",
|
||||||
"/search/",
|
"/search/",
|
||||||
"/search/:query",
|
"/search/:query",
|
||||||
|
"/search/gin-gonic",
|
||||||
|
"/search/google",
|
||||||
"/user_:name",
|
"/user_:name",
|
||||||
"/user_:name/about",
|
"/user_:name/about",
|
||||||
"/files/:dir/*filepath",
|
"/files/:dir/*filepath",
|
||||||
@ -148,6 +158,40 @@ func TestTreeWildcard(t *testing.T) {
|
|||||||
"/doc/go1.html",
|
"/doc/go1.html",
|
||||||
"/info/:user/public",
|
"/info/:user/public",
|
||||||
"/info/:user/project/:project",
|
"/info/:user/project/:project",
|
||||||
|
"/info/:user/project/golang",
|
||||||
|
"/aa/*xx",
|
||||||
|
"/ab/*xx",
|
||||||
|
"/:cc",
|
||||||
|
"/c1/:dd/e",
|
||||||
|
"/c1/:dd/e1",
|
||||||
|
"/:cc/cc",
|
||||||
|
"/:cc/:dd/ee",
|
||||||
|
"/:cc/:dd/:ee/ff",
|
||||||
|
"/:cc/:dd/:ee/:ff/gg",
|
||||||
|
"/:cc/:dd/:ee/:ff/:gg/hh",
|
||||||
|
"/get/test/abc/",
|
||||||
|
"/get/:param/abc/",
|
||||||
|
"/something/:paramname/thirdthing",
|
||||||
|
"/something/secondthing/test",
|
||||||
|
"/get/abc",
|
||||||
|
"/get/:param",
|
||||||
|
"/get/abc/123abc",
|
||||||
|
"/get/abc/:param",
|
||||||
|
"/get/abc/123abc/xxx8",
|
||||||
|
"/get/abc/123abc/:param",
|
||||||
|
"/get/abc/123abc/xxx8/1234",
|
||||||
|
"/get/abc/123abc/xxx8/:param",
|
||||||
|
"/get/abc/123abc/xxx8/1234/ffas",
|
||||||
|
"/get/abc/123abc/xxx8/1234/:param",
|
||||||
|
"/get/abc/123abc/xxx8/1234/kkdd/12c",
|
||||||
|
"/get/abc/123abc/xxx8/1234/kkdd/:param",
|
||||||
|
"/get/abc/:param/test",
|
||||||
|
"/get/abc/123abd/:param",
|
||||||
|
"/get/abc/123abddd/:param",
|
||||||
|
"/get/abc/123/:param",
|
||||||
|
"/get/abc/123abg/:param",
|
||||||
|
"/get/abc/123abf/:param",
|
||||||
|
"/get/abc/123abfff/:param",
|
||||||
}
|
}
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
tree.addRoute(route, fakeHandler(route))
|
tree.addRoute(route, fakeHandler(route))
|
||||||
@ -155,19 +199,122 @@ func TestTreeWildcard(t *testing.T) {
|
|||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
checkRequests(t, tree, testRequests{
|
||||||
{"/", false, "/", nil},
|
{"/", false, "/", nil},
|
||||||
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}},
|
{"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||||
{"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}},
|
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||||
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
|
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
|
||||||
|
{"/cmd/who", true, "/cmd/:tool/", Params{Param{"tool", "who"}}},
|
||||||
|
{"/cmd/who/", false, "/cmd/:tool/", Params{Param{"tool", "who"}}},
|
||||||
|
{"/cmd/whoami", false, "/cmd/whoami", nil},
|
||||||
|
{"/cmd/whoami/", true, "/cmd/whoami", nil},
|
||||||
|
{"/cmd/whoami/r", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
|
||||||
|
{"/cmd/whoami/r/", true, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
|
||||||
|
{"/cmd/whoami/root", false, "/cmd/whoami/root", nil},
|
||||||
|
{"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil},
|
||||||
{"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
|
{"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
|
||||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
|
{"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
|
||||||
{"/search/", false, "/search/", nil},
|
{"/search/", false, "/search/", nil},
|
||||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
||||||
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
||||||
|
{"/search/gin", false, "/search/:query", Params{Param{"query", "gin"}}},
|
||||||
|
{"/search/gin-gonic", false, "/search/gin-gonic", nil},
|
||||||
|
{"/search/google", false, "/search/google", nil},
|
||||||
{"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
|
{"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
|
||||||
{"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
|
{"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
|
||||||
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
|
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
|
||||||
{"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
|
{"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
|
||||||
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
|
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
|
||||||
|
{"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
|
||||||
|
{"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
|
||||||
|
{"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
|
||||||
|
{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||||
|
// * Error with argument being intercepted
|
||||||
|
// new PR handle (/all /all/cc /a/cc)
|
||||||
|
// fix PR: https://github.com/gin-gonic/gin/pull/2796
|
||||||
|
{"/all", false, "/:cc", Params{Param{Key: "cc", Value: "all"}}},
|
||||||
|
{"/d", false, "/:cc", Params{Param{Key: "cc", Value: "d"}}},
|
||||||
|
{"/ad", false, "/:cc", Params{Param{Key: "cc", Value: "ad"}}},
|
||||||
|
{"/dd", false, "/:cc", Params{Param{Key: "cc", Value: "dd"}}},
|
||||||
|
{"/dddaa", false, "/:cc", Params{Param{Key: "cc", Value: "dddaa"}}},
|
||||||
|
{"/aa", false, "/:cc", Params{Param{Key: "cc", Value: "aa"}}},
|
||||||
|
{"/aaa", false, "/:cc", Params{Param{Key: "cc", Value: "aaa"}}},
|
||||||
|
{"/aaa/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "aaa"}}},
|
||||||
|
{"/ab", false, "/:cc", Params{Param{Key: "cc", Value: "ab"}}},
|
||||||
|
{"/abb", false, "/:cc", Params{Param{Key: "cc", Value: "abb"}}},
|
||||||
|
{"/abb/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "abb"}}},
|
||||||
|
{"/allxxxx", false, "/:cc", Params{Param{Key: "cc", Value: "allxxxx"}}},
|
||||||
|
{"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}},
|
||||||
|
{"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}},
|
||||||
|
{"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||||
|
{"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}},
|
||||||
|
{"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}},
|
||||||
|
{"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}},
|
||||||
|
{"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}},
|
||||||
|
{"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}},
|
||||||
|
{"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}},
|
||||||
|
{"/acllcc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "acllcc"}}},
|
||||||
|
{"/get/test/abc/", false, "/get/test/abc/", nil},
|
||||||
|
{"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}},
|
||||||
|
{"/get/testaa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "testaa"}}},
|
||||||
|
{"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}},
|
||||||
|
{"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}},
|
||||||
|
{"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}},
|
||||||
|
{"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
|
||||||
|
{"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
|
||||||
|
{"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
|
||||||
|
{"/something/secondthing/test", false, "/something/secondthing/test", nil},
|
||||||
|
{"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
|
||||||
|
{"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}},
|
||||||
|
{"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}},
|
||||||
|
{"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}},
|
||||||
|
{"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}},
|
||||||
|
{"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}},
|
||||||
|
{"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}},
|
||||||
|
{"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}},
|
||||||
|
{"/cc/dd/ee/ff/gg/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "cc"}, Param{Key: "dd", Value: "dd"}, Param{Key: "ee", Value: "ee"}, Param{Key: "ff", Value: "ff"}, Param{Key: "gg", Value: "gg"}}},
|
||||||
|
{"/get/abc", false, "/get/abc", nil},
|
||||||
|
{"/get/a", false, "/get/:param", Params{Param{Key: "param", Value: "a"}}},
|
||||||
|
{"/get/abz", false, "/get/:param", Params{Param{Key: "param", Value: "abz"}}},
|
||||||
|
{"/get/12a", false, "/get/:param", Params{Param{Key: "param", Value: "12a"}}},
|
||||||
|
{"/get/abcd", false, "/get/:param", Params{Param{Key: "param", Value: "abcd"}}},
|
||||||
|
{"/get/abc/123abc", false, "/get/abc/123abc", nil},
|
||||||
|
{"/get/abc/12", false, "/get/abc/:param", Params{Param{Key: "param", Value: "12"}}},
|
||||||
|
{"/get/abc/123ab", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123ab"}}},
|
||||||
|
{"/get/abc/xyz", false, "/get/abc/:param", Params{Param{Key: "param", Value: "xyz"}}},
|
||||||
|
{"/get/abc/123abcddxx", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123abcddxx"}}},
|
||||||
|
{"/get/abc/123abc/xxx8", false, "/get/abc/123abc/xxx8", nil},
|
||||||
|
{"/get/abc/123abc/x", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "x"}}},
|
||||||
|
{"/get/abc/123abc/xxx", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx"}}},
|
||||||
|
{"/get/abc/123abc/abc", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "abc"}}},
|
||||||
|
{"/get/abc/123abc/xxx8xxas", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx8xxas"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234", false, "/get/abc/123abc/xxx8/1234", nil},
|
||||||
|
{"/get/abc/123abc/xxx8/1", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/123", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "123"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/78k", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "78k"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234xxxd", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1234xxxd"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/ffas", false, "/get/abc/123abc/xxx8/1234/ffas", nil},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/f", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "f"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/ffa", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffa"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kka", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "kka"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/ffas321", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffas321"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/12c", false, "/get/abc/123abc/xxx8/1234/kkdd/12c", nil},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/1", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "1"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/12", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/12b", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12b"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/34", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "34"}}},
|
||||||
|
{"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12c2e3"}}},
|
||||||
|
{"/get/abc/12/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "12"}}},
|
||||||
|
{"/get/abc/123abdd/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdd"}}},
|
||||||
|
{"/get/abc/123abdddf/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdddf"}}},
|
||||||
|
{"/get/abc/123ab/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123ab"}}},
|
||||||
|
{"/get/abc/123abgg/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abgg"}}},
|
||||||
|
{"/get/abc/123abff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abff"}}},
|
||||||
|
{"/get/abc/123abffff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abffff"}}},
|
||||||
|
{"/get/abc/123abd/test", false, "/get/abc/123abd/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||||
|
{"/get/abc/123abddd/test", false, "/get/abc/123abddd/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||||
|
{"/get/abc/123/test22", false, "/get/abc/123/:param", Params{Param{Key: "param", Value: "test22"}}},
|
||||||
|
{"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||||
|
{"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}},
|
||||||
|
{"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkPriorities(t, tree)
|
checkPriorities(t, tree)
|
||||||
@ -210,7 +357,7 @@ func TestUnescapeParameters(t *testing.T) {
|
|||||||
checkPriorities(t, tree)
|
checkPriorities(t, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func catchPanic(testFunc func()) (recv interface{}) {
|
func catchPanic(testFunc func()) (recv any) {
|
||||||
defer func() {
|
defer func() {
|
||||||
recv = recover()
|
recv = recover()
|
||||||
}()
|
}()
|
||||||
@ -245,20 +392,38 @@ func testRoutes(t *testing.T, routes []testRoute) {
|
|||||||
func TestTreeWildcardConflict(t *testing.T) {
|
func TestTreeWildcardConflict(t *testing.T) {
|
||||||
routes := []testRoute{
|
routes := []testRoute{
|
||||||
{"/cmd/:tool/:sub", false},
|
{"/cmd/:tool/:sub", false},
|
||||||
{"/cmd/vet", true},
|
{"/cmd/vet", false},
|
||||||
|
{"/foo/bar", false},
|
||||||
|
{"/foo/:name", false},
|
||||||
|
{"/foo/:names", true},
|
||||||
|
{"/cmd/*path", true},
|
||||||
|
{"/cmd/:badvar", true},
|
||||||
|
{"/cmd/:tool/names", false},
|
||||||
|
{"/cmd/:tool/:badsub/details", true},
|
||||||
{"/src/*filepath", false},
|
{"/src/*filepath", false},
|
||||||
|
{"/src/:file", true},
|
||||||
|
{"/src/static.json", true},
|
||||||
{"/src/*filepathx", true},
|
{"/src/*filepathx", true},
|
||||||
{"/src/", true},
|
{"/src/", true},
|
||||||
|
{"/src/foo/bar", true},
|
||||||
{"/src1/", false},
|
{"/src1/", false},
|
||||||
{"/src1/*filepath", true},
|
{"/src1/*filepath", true},
|
||||||
{"/src2*filepath", true},
|
{"/src2*filepath", true},
|
||||||
|
{"/src2/*filepath", false},
|
||||||
{"/search/:query", false},
|
{"/search/:query", false},
|
||||||
{"/search/invalid", true},
|
{"/search/valid", false},
|
||||||
{"/user_:name", false},
|
{"/user_:name", false},
|
||||||
{"/user_x", true},
|
{"/user_x", false},
|
||||||
{"/user_:name", false},
|
{"/user_:name", false},
|
||||||
{"/id:id", false},
|
{"/id:id", false},
|
||||||
{"/id/:id", true},
|
{"/id/:id", false},
|
||||||
|
}
|
||||||
|
testRoutes(t, routes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCatchAllAfterSlash(t *testing.T) {
|
||||||
|
routes := []testRoute{
|
||||||
|
{"/non-leading-*catchall", true},
|
||||||
}
|
}
|
||||||
testRoutes(t, routes)
|
testRoutes(t, routes)
|
||||||
}
|
}
|
||||||
@ -266,20 +431,23 @@ func TestTreeWildcardConflict(t *testing.T) {
|
|||||||
func TestTreeChildConflict(t *testing.T) {
|
func TestTreeChildConflict(t *testing.T) {
|
||||||
routes := []testRoute{
|
routes := []testRoute{
|
||||||
{"/cmd/vet", false},
|
{"/cmd/vet", false},
|
||||||
{"/cmd/:tool/:sub", true},
|
{"/cmd/:tool", false},
|
||||||
|
{"/cmd/:tool/:sub", false},
|
||||||
|
{"/cmd/:tool/misc", false},
|
||||||
|
{"/cmd/:tool/:othersub", true},
|
||||||
{"/src/AUTHORS", false},
|
{"/src/AUTHORS", false},
|
||||||
{"/src/*filepath", true},
|
{"/src/*filepath", true},
|
||||||
{"/user_x", false},
|
{"/user_x", false},
|
||||||
{"/user_:name", true},
|
{"/user_:name", false},
|
||||||
{"/id/:id", false},
|
{"/id/:id", false},
|
||||||
{"/id:id", true},
|
{"/id:id", false},
|
||||||
{"/:id", true},
|
{"/:id", false},
|
||||||
{"/*filepath", true},
|
{"/*filepath", true},
|
||||||
}
|
}
|
||||||
testRoutes(t, routes)
|
testRoutes(t, routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTreeDupliatePath(t *testing.T) {
|
func TestTreeDuplicatePath(t *testing.T) {
|
||||||
tree := &node{}
|
tree := &node{}
|
||||||
|
|
||||||
routes := [...]string{
|
routes := [...]string{
|
||||||
@ -419,7 +587,15 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|||||||
"/doc/go1.html",
|
"/doc/go1.html",
|
||||||
"/no/a",
|
"/no/a",
|
||||||
"/no/b",
|
"/no/b",
|
||||||
"/api/hello/:name",
|
"/api/:page/:name",
|
||||||
|
"/api/hello/:name/bar/",
|
||||||
|
"/api/bar/:name",
|
||||||
|
"/api/baz/foo",
|
||||||
|
"/api/baz/foo/bar",
|
||||||
|
"/blog/:p",
|
||||||
|
"/posts/:b/:c",
|
||||||
|
"/posts/b/:c/d/",
|
||||||
|
"/vendor/:x/*y",
|
||||||
}
|
}
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
recv := catchPanic(func() {
|
recv := catchPanic(func() {
|
||||||
@ -445,9 +621,22 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|||||||
"/admin/config/",
|
"/admin/config/",
|
||||||
"/admin/config/permissions/",
|
"/admin/config/permissions/",
|
||||||
"/doc/",
|
"/doc/",
|
||||||
|
"/admin/static/",
|
||||||
|
"/admin/cfg/",
|
||||||
|
"/admin/cfg/users/",
|
||||||
|
"/api/hello/x/bar",
|
||||||
|
"/api/baz/foo/",
|
||||||
|
"/api/baz/bax/",
|
||||||
|
"/api/bar/huh/",
|
||||||
|
"/api/baz/foo/bar/",
|
||||||
|
"/api/world/abc/",
|
||||||
|
"/blog/pp/",
|
||||||
|
"/posts/b/c/d",
|
||||||
|
"/vendor/x",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range tsrRoutes {
|
for _, route := range tsrRoutes {
|
||||||
value := tree.getValue(route, nil, false)
|
value := tree.getValue(route, nil, getSkippedNodes(), false)
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler for TSR route '%s", route)
|
t.Fatalf("non-nil handler for TSR route '%s", route)
|
||||||
} else if !value.tsr {
|
} else if !value.tsr {
|
||||||
@ -461,10 +650,14 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|||||||
"/no/",
|
"/no/",
|
||||||
"/_",
|
"/_",
|
||||||
"/_/",
|
"/_/",
|
||||||
"/api/world/abc",
|
"/api",
|
||||||
|
"/api/",
|
||||||
|
"/api/hello/x/foo",
|
||||||
|
"/api/baz/foo/bad",
|
||||||
|
"/foo/p/p",
|
||||||
}
|
}
|
||||||
for _, route := range noTsrRoutes {
|
for _, route := range noTsrRoutes {
|
||||||
value := tree.getValue(route, nil, false)
|
value := tree.getValue(route, nil, getSkippedNodes(), false)
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
||||||
} else if value.tsr {
|
} else if value.tsr {
|
||||||
@ -483,7 +676,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
|
|||||||
t.Fatalf("panic inserting test route: %v", recv)
|
t.Fatalf("panic inserting test route: %v", recv)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := tree.getValue("/", nil, false)
|
value := tree.getValue("/", nil, getSkippedNodes(), false)
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler")
|
t.Fatalf("non-nil handler")
|
||||||
} else if value.tsr {
|
} else if value.tsr {
|
||||||
@ -663,7 +856,7 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
|||||||
|
|
||||||
// normal lookup
|
// normal lookup
|
||||||
recv := catchPanic(func() {
|
recv := catchPanic(func() {
|
||||||
tree.getValue("/test", nil, false)
|
tree.getValue("/test", nil, getSkippedNodes(), false)
|
||||||
})
|
})
|
||||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||||
@ -678,6 +871,19 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeInvalidParamsType(t *testing.T) {
|
||||||
|
tree := &node{}
|
||||||
|
tree.wildChild = true
|
||||||
|
tree.children = append(tree.children, &node{})
|
||||||
|
tree.children[0].nType = 2
|
||||||
|
|
||||||
|
// set invalid Params type
|
||||||
|
params := make(Params, 0)
|
||||||
|
|
||||||
|
// try to trigger slice bounds out of range with capacity 0
|
||||||
|
tree.getValue("/test", ¶ms, getSkippedNodes(), false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTreeWildcardConflictEx(t *testing.T) {
|
func TestTreeWildcardConflictEx(t *testing.T) {
|
||||||
conflicts := [...]struct {
|
conflicts := [...]struct {
|
||||||
route string
|
route string
|
||||||
@ -688,8 +894,7 @@ func TestTreeWildcardConflictEx(t *testing.T) {
|
|||||||
{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
|
{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
|
||||||
{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
|
{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
|
||||||
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
|
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
|
||||||
{"/conxxx", "xxx", `/con:tact`, `:tact`},
|
{"/con:nection", ":nection", `/con:tact`, `:tact`},
|
||||||
{"/conooo/xxx", "ooo", `/con:tact`, `:tact`},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, conflict := range conflicts {
|
for _, conflict := range conflicts {
|
||||||
|
|||||||
19
utils.go
19
utils.go
@ -12,13 +12,14 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BindKey indicates a default bind key.
|
// BindKey indicates a default bind key.
|
||||||
const BindKey = "_gin-gonic/gin/bindkey"
|
const BindKey = "_gin-gonic/gin/bindkey"
|
||||||
|
|
||||||
// Bind is a helper function for given interface object and returns a Gin middleware.
|
// Bind is a helper function for given interface object and returns a Gin middleware.
|
||||||
func Bind(val interface{}) HandlerFunc {
|
func Bind(val any) HandlerFunc {
|
||||||
value := reflect.ValueOf(val)
|
value := reflect.ValueOf(val)
|
||||||
if value.Kind() == reflect.Ptr {
|
if value.Kind() == reflect.Ptr {
|
||||||
panic(`Bind struct can not be a pointer. Example:
|
panic(`Bind struct can not be a pointer. Example:
|
||||||
@ -50,7 +51,7 @@ func WrapH(h http.Handler) HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// H is a shortcut for map[string]interface{}
|
// H is a shortcut for map[string]interface{}
|
||||||
type H map[string]interface{}
|
type H map[string]any
|
||||||
|
|
||||||
// MarshalXML allows type H to be used with xml.Marshal.
|
// MarshalXML allows type H to be used with xml.Marshal.
|
||||||
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
@ -89,7 +90,7 @@ func filterFlags(content string) string {
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
func chooseData(custom, wildcard interface{}) interface{} {
|
func chooseData(custom, wildcard any) any {
|
||||||
if custom != nil {
|
if custom != nil {
|
||||||
return custom
|
return custom
|
||||||
}
|
}
|
||||||
@ -120,7 +121,7 @@ func lastChar(str string) uint8 {
|
|||||||
return str[len(str)-1]
|
return str[len(str)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func nameOfFunction(f interface{}) string {
|
func nameOfFunction(f any) string {
|
||||||
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,3 +152,13 @@ func resolveAddress(addr []string) string {
|
|||||||
panic("too many parameters")
|
panic("too many parameters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters
|
||||||
|
func isASCII(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] > unicode.MaxASCII {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@ -45,11 +45,11 @@ func TestWrap(t *testing.T) {
|
|||||||
fmt.Fprint(w, "hola!")
|
fmt.Fprint(w, "hola!")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
w := performRequest(router, "POST", "/path")
|
w := PerformRequest(router, "POST", "/path")
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "hello", w.Body.String())
|
assert.Equal(t, "hello", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = PerformRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, "hola!", w.Body.String())
|
assert.Equal(t, "hola!", w.Body.String())
|
||||||
}
|
}
|
||||||
@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) {
|
|||||||
called = true
|
called = true
|
||||||
value = c.MustGet(BindKey).(*bindTestStruct)
|
value = c.MustGet(BindKey).(*bindTestStruct)
|
||||||
})
|
})
|
||||||
performRequest(router, "GET", "/?foo=hola&bar=10")
|
PerformRequest(router, "GET", "/?foo=hola&bar=10")
|
||||||
assert.True(t, called)
|
assert.True(t, called)
|
||||||
assert.Equal(t, "hola", value.Foo)
|
assert.Equal(t, "hola", value.Foo)
|
||||||
assert.Equal(t, 10, value.Bar)
|
assert.Equal(t, 10, value.Bar)
|
||||||
|
|
||||||
called = false
|
called = false
|
||||||
performRequest(router, "GET", "/?foo=hola&bar=1")
|
PerformRequest(router, "GET", "/?foo=hola&bar=1")
|
||||||
assert.False(t, called)
|
assert.False(t, called)
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
@ -143,3 +143,8 @@ func TestMarshalXMLforH(t *testing.T) {
|
|||||||
e := h.MarshalXML(enc, x)
|
e := h.MarshalXML(enc, x)
|
||||||
assert.Error(t, e)
|
assert.Error(t, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsASCII(t *testing.T) {
|
||||||
|
assert.Equal(t, isASCII("test"), true)
|
||||||
|
assert.Equal(t, isASCII("🧡💛💚💙💜"), false)
|
||||||
|
}
|
||||||
|
|||||||
@ -5,4 +5,4 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
// Version is the current gin framework's version.
|
// Version is the current gin framework's version.
|
||||||
const Version = "v1.6.3"
|
const Version = "v1.8.1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user