mirror of
https://github.com/gin-gonic/gin.git
synced 2025-04-06 03:57:46 +08:00
Compare commits
381 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8763f33c65 | ||
|
e737e3e267 | ||
|
4ccfa7c275 | ||
|
90cf460269 | ||
|
ebe5e2a6bf | ||
|
733ee094fc | ||
|
a4baac6e5e | ||
|
1eb827240e | ||
|
3b28645dc9 | ||
|
c3c8620a7f | ||
|
3f818c3fa6 | ||
|
23d6961aeb | ||
|
e2e80f3347 | ||
|
e46bd52185 | ||
|
e8d34d053f | ||
|
02c1144f31 | ||
|
f875d87283 | ||
|
c8a3adc657 | ||
|
ea53388e6e | ||
|
9d11234efe | ||
|
647311aba2 | ||
|
299c6f30e3 | ||
|
b080116a7f | ||
|
ad740d508f | ||
|
f05f966a08 | ||
|
9d7c0e9e1a | ||
|
f2c861a24f | ||
|
28e57f58b1 | ||
|
3cb30679b5 | ||
|
cc4e11438c | ||
|
5f55c6a711 | ||
|
626d55b0c0 | ||
|
9c081de9cd | ||
|
64ead9e6bd | ||
|
4621b7ac98 | ||
|
334160bab7 | ||
|
24d67647cb | ||
|
e0d46ded6c | ||
|
4f339e6a35 | ||
|
36b0dede4b | ||
|
3f5b0afa2a | ||
|
a569ed8f26 | ||
|
6ca8ddb1ae | ||
|
40131af124 | ||
|
c677ccc40a | ||
|
7e298066ba | ||
|
3ac729dc4a | ||
|
8791c96960 | ||
|
b1c1e7b572 | ||
|
7d147928ee | ||
|
f5f5da8fa0 | ||
|
8dd088927a | ||
|
e60113dc95 | ||
|
490accf5d7 | ||
|
75ccf94d60 | ||
|
39089af625 | ||
|
638aa19e7d | ||
|
a18219566c | ||
|
b4f66e965b | ||
|
f80ade7a4b | ||
|
0397e5e0c0 | ||
|
c6f90df4e0 | ||
|
8acbe657f1 | ||
|
56dc72c4d5 | ||
|
c964ad370b | ||
|
7a865dcf1d | ||
|
fd1faaded0 | ||
|
d4e4136488 | ||
|
8790d08909 | ||
|
78f4687875 | ||
|
2b1da2b0b3 | ||
|
0d9dbbb445 | ||
|
fd60a24ab7 | ||
|
ee70b30a97 | ||
|
990c44aebf | ||
|
861ffb9181 | ||
|
f70dd00b00 | ||
|
ab8042e9e5 | ||
|
1b3c085969 | ||
|
ac5e84d93c | ||
|
83fc7673f9 | ||
|
646312aef6 | ||
|
5f458dd1a6 | ||
|
97eab7d09a | ||
|
3ea8bd99fb | ||
|
09f8224593 | ||
|
f75144a356 | ||
|
9c61295efe | ||
|
ae15646aba | ||
|
739d2d9c80 | ||
|
ecdbbbe948 | ||
|
000fdb3ac9 | ||
|
bb3519d26f | ||
|
82bcd6d39b | ||
|
86ff4a64c7 | ||
|
e957d1abf1 | ||
|
3dc1cd6572 | ||
|
9f598a31aa | ||
|
c6ae2e6966 | ||
|
a64286a776 | ||
|
8ab47c694e | ||
|
4a40f8f1a4 | ||
|
857db39f82 | ||
|
160c1730ef | ||
|
53fbf4dbfb | ||
|
811f271a04 | ||
|
081b36ebdb | ||
|
386d244068 | ||
|
44d0dd7092 | ||
|
49f45a5427 | ||
|
0aeac86b05 | ||
|
bdde009dbb | ||
|
a481ee2897 | ||
|
c2ba8f19ec | ||
|
dc9cff732e | ||
|
e32b5e3a47 | ||
|
bb2d8cf486 | ||
|
d16fdb15fa | ||
|
62b50cfbc0 | ||
|
02e754be9c | ||
|
d4a64265f2 | ||
|
4ea0e648e3 | ||
|
bb1fc2e0fe | ||
|
2d4bbec941 | ||
|
9f5ecd4be4 | ||
|
20cd6bcfc4 | ||
|
6bdc725c8d | ||
|
1ab268989d | ||
|
6a0556ed5a | ||
|
eac2daac64 | ||
|
757a638b7b | ||
|
fe989b6a6f | ||
|
a889c58de7 | ||
|
de1c4ec546 | ||
|
457fabd7e1 | ||
|
d1b2408027 | ||
|
1e1f0b1e76 | ||
|
943e93cba0 | ||
|
0b5df9fc39 | ||
|
ea03e10384 | ||
|
4cee78f538 | ||
|
fc1c43298d | ||
|
81ac7d55a0 | ||
|
d07db174ac | ||
|
c1d06e3d08 | ||
|
bd82c9e351 | ||
|
0c96a20209 | ||
|
153b229fcc | ||
|
e02ae6ae61 | ||
|
c5fd06361b | ||
|
ea17875035 | ||
|
b2d4185eec | ||
|
8cd11c82e4 | ||
|
1660995a04 | ||
|
97082f8acc | ||
|
7cb151bb4c | ||
|
3010cbd7f4 | ||
|
47ae6ee386 | ||
|
8eb5f832ba | ||
|
c58e0d59ca | ||
|
79a61b9032 | ||
|
7626361587 | ||
|
c9b27249fb | ||
|
7d8fc1563b | ||
|
41f2669ebc | ||
|
82e1c53cc0 | ||
|
8659ab573c | ||
|
e868fd1d3d | ||
|
297b664cf8 | ||
|
2285aa5430 | ||
|
d4caeee7c7 | ||
|
f551d7d8c2 | ||
|
483ac2a63b | ||
|
cc367f9125 | ||
|
80cd679c43 | ||
|
8fe209a447 | ||
|
c629689591 | ||
|
6150c488e7 | ||
|
234a1d33f7 | ||
|
a0acf1df28 | ||
|
212267d671 | ||
|
b682b8a54e | ||
|
c4b3c2c23a | ||
|
aefae309a4 | ||
|
8edb7a71a1 | ||
|
3a6865ac03 | ||
|
55e27f1246 | ||
|
971fe21876 | ||
|
8b9c55e8b0 | ||
|
51aea73ba0 | ||
|
33ab0fc155 | ||
|
45c758e2f9 | ||
|
24a1d2adb9 | ||
|
4c64f1c385 | ||
|
fa58bff301 | ||
|
6296175f70 | ||
|
6fab4c373e | ||
|
78dad9d77d | ||
|
814cd188eb | ||
|
2c9e5fe47a | ||
|
0128d74f34 | ||
|
de1f142ed4 | ||
|
2ae6157049 | ||
|
fb13e822a4 | ||
|
1c48977cca | ||
|
de17fb1a33 | ||
|
b04917c53e | ||
|
1b5ba251cf | ||
|
ad66d9d11a | ||
|
8374ed2268 | ||
|
79dd72deb9 | ||
|
c35bde97d5 | ||
|
b57163a0e4 | ||
|
e837e1cd18 | ||
|
680be7d928 | ||
|
088cdd74d4 | ||
|
92dd245c9b | ||
|
6de2245e62 | ||
|
12b55b4fe9 | ||
|
815122a0f4 | ||
|
05caa5c00e | ||
|
6c3a1d7063 | ||
|
f2182de38c | ||
|
ed049dd850 | ||
|
f197a8bae0 | ||
|
92ba8e17aa | ||
|
58303bde7d | ||
|
5fa34529ae | ||
|
38eb5acc6b | ||
|
60e24d5690 | ||
|
4b68a5f12a | ||
|
ed03102ef0 | ||
|
aa6002134e | ||
|
87811a97bd | ||
|
f1e942889a | ||
|
ef687e0db2 | ||
|
90e7073d56 | ||
|
c131704fd6 | ||
|
e61cc06955 | ||
|
d8e053d15f | ||
|
c706ace929 | ||
|
444e156fb1 | ||
|
696d37e030 | ||
|
493b12482b | ||
|
6854212698 | ||
|
888b14ab28 | ||
|
c4580944ae | ||
|
6a1d279c28 | ||
|
3d55efe419 | ||
|
205bb8151c | ||
|
865fd560fc | ||
|
be0d86edf4 | ||
|
62265c893c | ||
|
2bde107686 | ||
|
9701b651b7 | ||
|
1e24473f5f | ||
|
d8dfaaeb2e | ||
|
f073e33fb9 | ||
|
fcd36c549d | ||
|
417b142703 | ||
|
8860527de6 | ||
|
90330e2a76 | ||
|
7927a45143 | ||
|
ecadd82592 | ||
|
5f0b6cdfc4 | ||
|
3757142584 | ||
|
87e40d6b15 | ||
|
b40ded1837 | ||
|
c19374c471 | ||
|
b94075ff1d | ||
|
41d38fb68c | ||
|
580e7da6ee | ||
|
1b28e2b030 | ||
|
336ce0d475 | ||
|
6868ed18cc | ||
|
94153d1e19 | ||
|
01363191be | ||
|
d062a6a615 | ||
|
1538ece13f | ||
|
8a0f95cc71 | ||
|
92a988d761 | ||
|
fb5f045417 | ||
|
7d189814cb | ||
|
e56d18f19d | ||
|
3973c77263 | ||
|
ba7e58989c | ||
|
504ec594f8 | ||
|
0be805a675 | ||
|
830a63d244 | ||
|
bc2417fc40 | ||
|
a06d546f5c | ||
|
f068099d0d | ||
|
ffb3b73430 | ||
|
823adfc91a | ||
|
4d7c4ec36f | ||
|
57ede9c95a | ||
|
6aee45608d | ||
|
89a159bdd9 | ||
|
efa3175007 | ||
|
cbdd47a7e1 | ||
|
d4e72a17f7 | ||
|
1c2aa59b20 | ||
|
eb75ce0ff5 | ||
|
2d3d6d2f13 | ||
|
3fe928994b | ||
|
34ae7777ca | ||
|
464535ff94 | ||
|
5929d52171 | ||
|
21125bbb3f | ||
|
39181329de | ||
|
1a2bc0e7cb | ||
|
6d75aba83f | ||
|
97b3c0d88a | ||
|
f469c1be39 | ||
|
d6534ccf38 | ||
|
ef168679ce | ||
|
e052bf31aa | ||
|
71f7087097 | ||
|
3a6f18f32f | ||
|
ae349b4015 | ||
|
eab47b5423 | ||
|
deb83b6365 | ||
|
5c62979390 | ||
|
abcf32f5ad | ||
|
a550c568d7 | ||
|
e4c026e2a1 | ||
|
30cdbfcf4c | ||
|
4e7584175d | ||
|
dfc25f91e0 | ||
|
8200903817 | ||
|
c303020561 | ||
|
527d950252 | ||
|
f3a6b69fd0 | ||
|
a46dee3a9a | ||
|
435a76b735 | ||
|
b463b1c2a1 | ||
|
6ebb945bd7 | ||
|
9a575a4c05 | ||
|
11aa11a656 | ||
|
0a55865c3f | ||
|
d4ca9a0fb1 | ||
|
caf2802593 | ||
|
f96678cb6b | ||
|
3116a2d7a1 | ||
|
c7a28f8532 | ||
|
9d2883ef47 | ||
|
9c27053243 | ||
|
372cc4a010 | ||
|
690aa2b1b9 | ||
|
e3ee01d185 | ||
|
1d0f938f28 | ||
|
f2bbdfe9f2 | ||
|
7834a03e84 | ||
|
09f6cff92a | ||
|
be860ec157 | ||
|
dd8a27c0b6 | ||
|
fb8a113f8d | ||
|
a8857ed70a | ||
|
61a0cda75a | ||
|
34ce2104ca | ||
|
97a32b1de3 | ||
|
6703dea51c | ||
|
0cbb30aa94 | ||
|
b5ca989875 | ||
|
328d0b8076 | ||
|
f07a4f8aea | ||
|
168edcad80 | ||
|
f13e53bb92 | ||
|
afb38396b5 | ||
|
e72e584d1a | ||
|
d7091e7dec | ||
|
2921582d11 | ||
|
4fe5f3e4b4 | ||
|
5452a1d3ef | ||
|
1acb459c10 | ||
|
215c9ce231 | ||
|
c0418c48e4 | ||
|
f1da692fbd | ||
|
7313b8fddc | ||
|
77649bcfee | ||
|
ee4de846a8 |
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -30,7 +30,7 @@ func main() {
|
||||
|
||||
<!-- Your expectation result of 'curl' command, like -->
|
||||
```
|
||||
$ curl http://localhost:8201/hello/world
|
||||
$ curl http://localhost:9000/hello/world
|
||||
Hello world
|
||||
```
|
||||
|
||||
@ -38,7 +38,7 @@ Hello world
|
||||
|
||||
<!-- Actual result showing the problem -->
|
||||
```
|
||||
$ curl -i http://localhost:8201/hello/world
|
||||
$ curl -i http://localhost:9000/hello/world
|
||||
<YOUR RESULT>
|
||||
```
|
||||
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,7 +1,7 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- 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.
|
||||
- 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@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
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@v3
|
83
.github/workflows/gin.yml
vendored
Normal file
83
.github/workflows/gin.yml
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1"
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.61.0
|
||||
args: --verbose
|
||||
test:
|
||||
needs: lint
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
go: ["1.23", "1.24"]
|
||||
test-tags:
|
||||
[
|
||||
"",
|
||||
"-tags nomsgpack",
|
||||
'--ldflags="-checklinkname=0" -tags "sonic avx"',
|
||||
"-tags go_json",
|
||||
"-race",
|
||||
]
|
||||
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@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
cache: false
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
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@v4
|
||||
with:
|
||||
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
31
.github/workflows/goreleaser.yml
vendored
Normal file
31
.github/workflows/goreleaser.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1"
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,3 +5,7 @@ count.out
|
||||
test
|
||||
profile.out
|
||||
tmp.out
|
||||
|
||||
# Develop tools
|
||||
.idea/
|
||||
.vscode/
|
||||
|
71
.golangci.yml
Normal file
71
.golangci.yml
Normal file
@ -0,0 +1,71 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck
|
||||
- dogsled
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- copyloopvar
|
||||
- gci
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosec
|
||||
- misspell
|
||||
- nakedret
|
||||
- nilerr
|
||||
- nolintlint
|
||||
- perfsprint
|
||||
- revive
|
||||
- testifylint
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
|
||||
linters-settings:
|
||||
gosec:
|
||||
# To select a subset of rules to run.
|
||||
# Available rules: https://github.com/securego/gosec#available-rules
|
||||
# Default: [] - means include all rules
|
||||
includes:
|
||||
- G102
|
||||
- G106
|
||||
- G108
|
||||
- G109
|
||||
- G111
|
||||
- G112
|
||||
- G201
|
||||
- G203
|
||||
perfsprint:
|
||||
err-error: true
|
||||
errorf: true
|
||||
int-conversion: true
|
||||
sprintf1: true
|
||||
strconcat: true
|
||||
testifylint:
|
||||
enable-all: true
|
||||
|
||||
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
|
||||
- linters:
|
||||
- revive
|
||||
path: _test\.go
|
||||
- path: gin.go
|
||||
linters:
|
||||
- gci
|
56
.goreleaser.yaml
Normal file
56
.goreleaser.yaml
Normal file
@ -0,0 +1,56 @@
|
||||
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.
|
||||
disable: 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: github
|
||||
|
||||
# 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: "Refactor"
|
||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
||||
order: 3
|
||||
- title: "Build process updates"
|
||||
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: "Documentation updates"
|
||||
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: Others
|
||||
order: 999
|
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
|
617
AUTHORS.md
617
AUTHORS.md
@ -2,232 +2,405 @@ List of all the awesome people working to make Gin the best Web Framework in Go.
|
||||
|
||||
## 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
|
||||
|
||||
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||
|
||||
------
|
||||
|
||||
People and companies, who have contributed, in alphabetical order.
|
||||
|
||||
**@858806258 (杰哥)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@achedeuzot (Klemen Sever)**
|
||||
- Fix newline debug printing
|
||||
|
||||
|
||||
**@adammck (Adam Mckaig)**
|
||||
- Add MIT license
|
||||
|
||||
|
||||
**@AlexanderChen1989 (Alexander)**
|
||||
- Typos in README
|
||||
|
||||
|
||||
**@alexanderdidenko (Aleksandr Didenko)**
|
||||
- Add support multipart/form-data
|
||||
|
||||
|
||||
**@alexandernyquist (Alexander Nyquist)**
|
||||
- Using template.Must to fix multiple return issue
|
||||
- ★ Added support for OPTIONS verb
|
||||
- ★ Setting response headers before calling WriteHeader
|
||||
- Improved documentation for model binding
|
||||
- ★ Added Content.Redirect()
|
||||
- ★ Added tons of Unit tests
|
||||
|
||||
|
||||
**@austinheap (Austin Heap)**
|
||||
- Added travis CI integration
|
||||
|
||||
|
||||
**@andredublin (Andre Dublin)**
|
||||
- Fix typo in comment
|
||||
|
||||
|
||||
**@bredov (Ludwig Valda Vasquez)**
|
||||
- Fix html templating in debug mode
|
||||
|
||||
|
||||
**@bluele (Jun Kimura)**
|
||||
- Fixes code examples in README
|
||||
|
||||
|
||||
**@chad-russell**
|
||||
- ★ Support for serializing gin.H into XML
|
||||
|
||||
|
||||
**@dickeyxxx (Jeff Dickey)**
|
||||
- Typos in README
|
||||
- Add example about serving static files
|
||||
|
||||
|
||||
**@donileo (Adonis)**
|
||||
- Add NoMethod handler
|
||||
|
||||
|
||||
**@dutchcoders (DutchCoders)**
|
||||
- ★ Fix security bug that allows client to spoof ip
|
||||
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
|
||||
|
||||
|
||||
**@el3ctro- (Joshua Loper)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@ethankan (Ethan Kan)**
|
||||
- Unsigned integers in binding
|
||||
|
||||
|
||||
**(Evgeny Persienko)**
|
||||
- Validate sub structures
|
||||
|
||||
|
||||
**@frankbille (Frank Bille)**
|
||||
- Add support for HTTP Realm Auth
|
||||
|
||||
|
||||
**@fmd (Fareed Dudhia)**
|
||||
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
||||
|
||||
|
||||
**@ironiridis (Christopher Harrington)**
|
||||
- Remove old reference
|
||||
|
||||
|
||||
**@jammie-stackhouse (Jamie Stackhouse)**
|
||||
- Add more shortcuts for router methods
|
||||
|
||||
|
||||
**@jasonrhansen**
|
||||
- Fix spelling and grammar errors in documentation
|
||||
|
||||
|
||||
**@JasonSoft (Jason Lee)**
|
||||
- Fix typo in comment
|
||||
|
||||
|
||||
**@joiggama (Ignacio Galindo)**
|
||||
- Add utf-8 charset header on renders
|
||||
|
||||
|
||||
**@julienschmidt (Julien Schmidt)**
|
||||
- gofmt the code examples
|
||||
|
||||
|
||||
**@kelcecil (Kel Cecil)**
|
||||
- Fix readme typo
|
||||
|
||||
|
||||
**@kyledinh (Kyle Dinh)**
|
||||
- Adds RunTLS()
|
||||
|
||||
|
||||
**@LinusU (Linus Unnebäck)**
|
||||
- Small fixes in README
|
||||
|
||||
|
||||
**@loongmxbt (Saint Asky)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@lucas-clemente (Lucas Clemente)**
|
||||
- ★ work around path.Join removing trailing slashes from routes
|
||||
|
||||
|
||||
**@mattn (Yasuhiro Matsumoto)**
|
||||
- Improve color logger
|
||||
|
||||
|
||||
**@mdigger (Dmitry Sedykh)**
|
||||
- Fixes Form binding when content-type is x-www-form-urlencoded
|
||||
- No repeat call c.Writer.Status() in gin.Logger
|
||||
- Fixes Content-Type for json render
|
||||
|
||||
|
||||
**@mirzac (Mirza Ceric)**
|
||||
- Fix debug printing
|
||||
|
||||
|
||||
**@mopemope (Yutaka Matsubara)**
|
||||
- ★ Adds Godep support (Dependencies Manager)
|
||||
- Fix variadic parameter in the flexible render API
|
||||
- Fix Corrupted plain render
|
||||
- Add Pluggable View Renderer Example
|
||||
|
||||
|
||||
**@msemenistyi (Mykyta Semenistyi)**
|
||||
- update Readme.md. Add code to String method
|
||||
|
||||
|
||||
**@msoedov (Sasha Myasoedov)**
|
||||
- ★ Adds tons of unit tests.
|
||||
|
||||
|
||||
**@ngerakines (Nick Gerakines)**
|
||||
- ★ Improves API, c.GET() doesn't panic
|
||||
- Adds MustGet() method
|
||||
|
||||
|
||||
**@r8k (Rajiv Kilaparti)**
|
||||
- Fix Port usage in README.
|
||||
|
||||
|
||||
**@rayrod2030 (Ray Rodriguez)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@rns**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@RobAWilkinson (Robert Wilkinson)**
|
||||
- Add example of forms and params
|
||||
|
||||
|
||||
**@rogierlommers (Rogier Lommers)**
|
||||
- Add updated static serve example
|
||||
|
||||
**@rw-access (Ross Wolf)**
|
||||
- Added support to mix exact and param routes
|
||||
|
||||
**@se77en (Damon Zhao)**
|
||||
- Improve color logging
|
||||
|
||||
|
||||
**@silasb (Silas Baronda)**
|
||||
- Fixing quotes in README
|
||||
|
||||
|
||||
**@SkuliOskarsson (Skuli Oskarsson)**
|
||||
- Fixes some texts in README II
|
||||
|
||||
|
||||
**@slimmy (Jimmy Pettersson)**
|
||||
- Added messages for required bindings
|
||||
|
||||
|
||||
**@smira (Andrey Smirnov)**
|
||||
- Add support for ignored/unexported fields in binding
|
||||
|
||||
|
||||
**@superalsrk (SRK.Lyu)**
|
||||
- Update httprouter godeps
|
||||
|
||||
|
||||
**@tebeka (Miki Tebeka)**
|
||||
- Use net/http constants instead of numeric values
|
||||
|
||||
|
||||
**@techjanitor**
|
||||
- Update context.go reserved IPs
|
||||
|
||||
|
||||
**@yosssi (Keiji Yoshida)**
|
||||
- Fix link in README
|
||||
|
||||
|
||||
**@yuyabee**
|
||||
- Fixed README
|
||||
- 178inaba <178inaba@users.noreply.github.com>
|
||||
- A. F <hello@clivern.com>
|
||||
- ABHISHEK SONI <abhishek.rocks26@gmail.com>
|
||||
- Abhishek Chanda <achanda@users.noreply.github.com>
|
||||
- Abner Chen <houjunchen@gmail.com>
|
||||
- AcoNCodes <acongame@gmail.com>
|
||||
- Adam Dratwinski <adam.dratwinski@gmail.com>
|
||||
- Adam Mckaig <adam.mckaig@gmail.com>
|
||||
- Adam Zielinski <MusicAdam@users.noreply.github.com>
|
||||
- Adonis <donileo@gmail.com>
|
||||
- Alan Wang <azzwacb9001@126.com>
|
||||
- Albin Gilles <gilles.albin@gmail.com>
|
||||
- Aleksandr Didenko <aa.didenko@yandex.ru>
|
||||
- Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
|
||||
- Alex <AWulkan@users.noreply.github.com>
|
||||
- Alexander <alexanderchenmh@gmail.com>
|
||||
- Alexander Lokhman <alex.lokhman@gmail.com>
|
||||
- Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com>
|
||||
- Alexander Nyquist <nyquist.alexander@gmail.com>
|
||||
- Allen Ren <kulong0105@gmail.com>
|
||||
- AllinGo <tanhp@outlook.com>
|
||||
- Ammar Bandukwala <ammar@ammar.io>
|
||||
- An Xiao (Luffy) <hac@zju.edu.cn>
|
||||
- Andre Dublin <81dublin@gmail.com>
|
||||
- Andrew Szeto <github@jabagawee.com>
|
||||
- Andrey Abramov <andreyabramov.aaa@gmail.com>
|
||||
- Andrey Nering <andrey.nering@gmail.com>
|
||||
- Andrey Smirnov <Smirnov.Andrey@gmail.com>
|
||||
- Andrii Bubis <firstrow@gmail.com>
|
||||
- André Bazaglia <bazaglia@users.noreply.github.com>
|
||||
- Andy Pan <panjf2000@gmail.com>
|
||||
- Antoine GIRARD <sapk@users.noreply.github.com>
|
||||
- Anup Kumar Panwar <1anuppanwar@gmail.com>
|
||||
- Aravinth Sundaram <gosh.aravind@gmail.com>
|
||||
- Artem <horechek@gmail.com>
|
||||
- Ashwani <ashwanisharma686@gmail.com>
|
||||
- Aurelien Regat-Barrel <arb@cyberkarma.net>
|
||||
- Austin Heap <me@austinheap.com>
|
||||
- Barnabus <jbampton@users.noreply.github.com>
|
||||
- Bo-Yi Wu <appleboy.tw@gmail.com>
|
||||
- Boris Borshevsky <BorisBorshevsky@gmail.com>
|
||||
- Boyi Wu <p581581@gmail.com>
|
||||
- BradyBromley <51128276+BradyBromley@users.noreply.github.com>
|
||||
- Brendan Fosberry <brendan@shopkeep.com>
|
||||
- Brian Wigginton <brianwigginton@gmail.com>
|
||||
- Carlos Eduardo <carlosedp@gmail.com>
|
||||
- Chad Russell <chaddouglasrussell@gmail.com>
|
||||
- Charles <cxjava@gmail.com>
|
||||
- Christian Muehlhaeuser <muesli@gmail.com>
|
||||
- Christian Persson <saser@live.se>
|
||||
- Christopher Harrington <ironiridis@gmail.com>
|
||||
- Damon Zhao <yijun.zhao@outlook.com>
|
||||
- Dan Markham <dmarkham@gmail.com>
|
||||
- Dang Nguyen <hoangdang.me@gmail.com>
|
||||
- Daniel Krom <kromdan@gmail.com>
|
||||
- Daniel M. Lambea <dmlambea@gmail.com>
|
||||
- Danieliu <liudanking@gmail.com>
|
||||
- David Irvine <aviddiviner@gmail.com>
|
||||
- David Zhang <crispgm@gmail.com>
|
||||
- Davor Kapsa <dvrkps@users.noreply.github.com>
|
||||
- DeathKing <DeathKing@users.noreply.github.com>
|
||||
- Dennis Cho <47404603+forest747@users.noreply.github.com>
|
||||
- Dmitry Dorogin <dmirogin@ya.ru>
|
||||
- Dmitry Kutakov <vkd.castle@gmail.com>
|
||||
- Dmitry Sedykh <dmitrys@d3h.local>
|
||||
- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com>
|
||||
- Donn Pebe <iam@donnpebe.com>
|
||||
- Dustin Decker <dustindecker@protonmail.com>
|
||||
- Eason Lin <easonlin404@gmail.com>
|
||||
- Edward Betts <edward@4angle.com>
|
||||
- Egor Seredin <4819888+agmt@users.noreply.github.com>
|
||||
- Emmanuel Goh <emmanuel@visenze.com>
|
||||
- Equim <sayaka@ekyu.moe>
|
||||
- Eren A. Akyol <eren@redmc.me>
|
||||
- Eric_Lee <xplzv@126.com>
|
||||
- Erik Bender <erik.bender@develerik.dev>
|
||||
- Ethan Kan <ethankan@neoplot.com>
|
||||
- Evgeny Persienko <e.persienko@office.ngs.ru>
|
||||
- Faisal Alam <ifaisalalam@gmail.com>
|
||||
- Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||
- Filip Figiel <figiel.filip@gmail.com>
|
||||
- Florian Polster <couchpolster@icqmail.com>
|
||||
- Frank Bille <github@frankbille.dk>
|
||||
- Franz Bettag <franz@bett.ag>
|
||||
- Ganlv <ganlvtech@users.noreply.github.com>
|
||||
- Gaozhen Ying <yinggaozhen@hotmail.com>
|
||||
- George Gabolaev <gabolaev98@gmail.com>
|
||||
- George Kirilenko <necryin@users.noreply.github.com>
|
||||
- Georges Varouchas <georges.varouchas@gmail.com>
|
||||
- Gordon Tyler <gordon@doxxx.net>
|
||||
- Harindu Perera <harinduenator@gmail.com>
|
||||
- Helios <674876158@qq.com>
|
||||
- Henry Kwan <piengeng@users.noreply.github.com>
|
||||
- Henry Yee <henry@yearning.io>
|
||||
- Himanshu Mishra <OrkoHunter@users.noreply.github.com>
|
||||
- Hiroyuki Tanaka <h.tanaka.0325@gmail.com>
|
||||
- Ibraheem Ahmed <ibrah1440@gmail.com>
|
||||
- Ignacio Galindo <joiggama@gmail.com>
|
||||
- Igor H. Vieira <zignd.igor@gmail.com>
|
||||
- Ildar1111 <54001462+Ildar1111@users.noreply.github.com>
|
||||
- Iskander (Alex) Sharipov <iskander.sharipov@intel.com>
|
||||
- Ismail Gjevori <isgjevori@protonmail.com>
|
||||
- Ivan Chen <allenivan@gmail.com>
|
||||
- JINNOUCHI Yasushi <delphinus@remora.cx>
|
||||
- James Pettyjohn <japettyjohn@users.noreply.github.com>
|
||||
- Jamie Stackhouse <jamie.stackhouse@redspace.com>
|
||||
- Jason Lee <jawc@hotmail.com>
|
||||
- Javier Provecho <j.provecho@dartekstudios.com>
|
||||
- Javier Provecho <javier.provecho@bq.com>
|
||||
- Javier Provecho <javiertitan@gmail.com>
|
||||
- Javier Provecho Fernandez <j.provecho@dartekstudios.com>
|
||||
- Javier Provecho Fernandez <javiertitan@gmail.com>
|
||||
- Jean-Christophe Lebreton <jclebreton@gmail.com>
|
||||
- Jeff <laojianzi1994@gmail.com>
|
||||
- Jeremy Loy <jeremy.b.loy@icloud.com>
|
||||
- Jim Filippou <p3160253@aueb.gr>
|
||||
- Jimmy Pettersson <jimmy@expertmaker.com>
|
||||
- John Bampton <jbampton@users.noreply.github.com>
|
||||
- Johnny Dallas <johnnydallas0308@gmail.com>
|
||||
- Johnny Dallas <theonlyjohnny@theonlyjohnny.sh>
|
||||
- Jonathan (JC) Chen <jc@dijonkitchen.org>
|
||||
- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com>
|
||||
- Josh Horowitz <joshua.m.horowitz@gmail.com>
|
||||
- Joshua Loper <josh.el3@gmail.com>
|
||||
- Julien Schmidt <github@julienschmidt.com>
|
||||
- Jun Kimura <jksmphone@gmail.com>
|
||||
- Justin Beckwith <justin.beckwith@gmail.com>
|
||||
- Justin Israel <justinisrael@gmail.com>
|
||||
- Justin Mayhew <mayhew@live.ca>
|
||||
- Jérôme Laforge <jerome-laforge@users.noreply.github.com>
|
||||
- Kacper Bąk <56700396+53jk1@users.noreply.github.com>
|
||||
- Kamron Batman <kamronbatman@users.noreply.github.com>
|
||||
- Kane Rogers <kane@cleanstream.com.au>
|
||||
- Kaushik Neelichetty <kaushikneelichetty6132@gmail.com>
|
||||
- Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||
- Kel Cecil <kel.cecil@listhub.com>
|
||||
- Kevin Mulvey <kmulvey@linux.com>
|
||||
- Kevin Zhu <ipandtcp@gmail.com>
|
||||
- Kirill Motkov <motkov.kirill@gmail.com>
|
||||
- Klemen Sever <ksever@student.42.fr>
|
||||
- Kristoffer A. Iversen <kristoffer.a.iversen@gmail.com>
|
||||
- Krzysztof Szafrański <k.p.szafranski@gmail.com>
|
||||
- Kumar McMillan <kumar.mcmillan@gmail.com>
|
||||
- Kyle Mcgill <email@kylescottmcgill.com>
|
||||
- Lanco <35420416+lancoLiu@users.noreply.github.com>
|
||||
- Levi Olson <olson.levi@gmail.com>
|
||||
- Lin Kao-Yuan <mosdeo@gmail.com>
|
||||
- Linus Unnebäck <linus@folkdatorn.se>
|
||||
- Lucas Clemente <lucas@clemente.io>
|
||||
- Ludwig Valda Vasquez <bredov@gmail.com>
|
||||
- Luis GG <lggomez@users.noreply.github.com>
|
||||
- MW Lim <williamchange@gmail.com>
|
||||
- Maksimov Sergey <konjoot@gmail.com>
|
||||
- Manjusaka <lizheao940510@gmail.com>
|
||||
- Manu MA <manu.mtza@gmail.com>
|
||||
- Manu MA <manu.valladolid@gmail.com>
|
||||
- Manu Mtz-Almeida <manu.valladolid@gmail.com>
|
||||
- Manu Mtz.-Almeida <manu.valladolid@gmail.com>
|
||||
- Manuel Alonso <manuelalonso@invisionapp.com>
|
||||
- Mara Kim <hacker.root@gmail.com>
|
||||
- Mario Kostelac <mario@intercom.io>
|
||||
- Martin Karlsch <martin@karlsch.com>
|
||||
- Matt Newberry <mnewberry@opentable.com>
|
||||
- Matt Williams <gh@mattyw.net>
|
||||
- Matthieu MOREL <mmorel-35@users.noreply.github.com>
|
||||
- Max Hilbrunner <mhilbrunner@users.noreply.github.com>
|
||||
- Maxime Soulé <btik-git@scoubidou.com>
|
||||
- MetalBreaker <johnymichelson@gmail.com>
|
||||
- Michael Puncel <mpuncel@squareup.com>
|
||||
- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com>
|
||||
- Mike <38686456+icy4ever@users.noreply.github.com>
|
||||
- Mike Stipicevic <mst@ableton.com>
|
||||
- Miki Tebeka <miki.tebeka@gmail.com>
|
||||
- Miles <MilesLin@users.noreply.github.com>
|
||||
- Mirza Ceric <mirza.ceric@b2match.com>
|
||||
- Mykyta Semenistyi <nikeiwe@gmail.com>
|
||||
- Naoki Takano <honten@tinkermode.com>
|
||||
- Ngalim Siregar <ngalim.siregar@gmail.com>
|
||||
- Ni Hao <supernihaooo@qq.com>
|
||||
- Nick Gerakines <nick@gerakines.net>
|
||||
- Nikifor Seryakov <nikandfor@gmail.com>
|
||||
- Notealot <714804968@qq.com>
|
||||
- Olivier Mengué <dolmen@cpan.org>
|
||||
- Olivier Robardet <orobardet@users.noreply.github.com>
|
||||
- Pablo Moncada <pablo.moncada@bq.com>
|
||||
- Pablo Moncada <pmoncadaisla@gmail.com>
|
||||
- Panmax <967168@qq.com>
|
||||
- Peperoncino <2wua4nlyi@gmail.com>
|
||||
- Philipp Meinen <philipp@bind.ch>
|
||||
- Pierre Massat <pierre@massat.io>
|
||||
- Qt <golang.chen@gmail.com>
|
||||
- Quentin ROYER <aydendevg@gmail.com>
|
||||
- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com>
|
||||
- Rafal Zajac <rzajac@gmail.com>
|
||||
- Rahul Datta Roy <rahuldroy@users.noreply.github.com>
|
||||
- Rajiv Kilaparti <rajivk085@gmail.com>
|
||||
- Raphael Gavache <raphael.gavache@datadoghq.com>
|
||||
- Ray Rodriguez <rayrod2030@gmail.com>
|
||||
- Regner Blok-Andersen <shadowdf@gmail.com>
|
||||
- Remco <remco@dutchcoders.io>
|
||||
- Rex Lee(李俊) <duguying2008@gmail.com>
|
||||
- Richard Lee <dlackty@gmail.com>
|
||||
- Riverside <wangyb65@gmail.com>
|
||||
- Robert Wilkinson <wilkinson.robert.a@gmail.com>
|
||||
- Rogier Lommers <rogier@lommers.org>
|
||||
- Rohan Pai <me@rohanpai.com>
|
||||
- Romain Beuque <rbeuque74@gmail.com>
|
||||
- Roman Belyakovsky <ihryamzik@gmail.com>
|
||||
- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com>
|
||||
- Roman Zaynetdinov <roman.zaynetdinov@lekane.com>
|
||||
- Ronald Petty <ronald.petty@rx-m.com>
|
||||
- Ross Wolf <31489089+rw-access@users.noreply.github.com>
|
||||
- Roy Lou <roylou@gmail.com>
|
||||
- Rubi <14269809+codenoid@users.noreply.github.com>
|
||||
- Ryan <46182144+ryanker@users.noreply.github.com>
|
||||
- Ryan J. Yoder <me@ryanjyoder.com>
|
||||
- SRK.Lyu <superalsrk@gmail.com>
|
||||
- Sai <sairoutine@gmail.com>
|
||||
- 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>
|
||||
|
261
CHANGELOG.md
261
CHANGELOG.md
@ -1,14 +1,246 @@
|
||||
# Gin ChangeLog
|
||||
|
||||
## Gin v1.10.0
|
||||
|
||||
### Features
|
||||
|
||||
* feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1)
|
||||
* feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost)
|
||||
* feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb)
|
||||
* feat(binding): support override default binding implement (#3514) (@ssfyn)
|
||||
* feat(engine): Added `OptionFunc` and `With` (#3572) (@flc1125)
|
||||
* feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Revert "fix(uri): query binding bug (#3236)" (#3899) (@appleboy)
|
||||
* fix(binding): binding error while not upload file (#3819) (#3820) (@clearcodecn)
|
||||
* fix(binding): dereference pointer to struct (#3199) (@echovl)
|
||||
* fix(context): make context Value method adhere to Go standards (#3897) (@FarmerChillax)
|
||||
* fix(engine): fix unit test (#3878) (@flc1125)
|
||||
* fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) (@Crocmagnon)
|
||||
* fix(route): Add fullPath in context copy (#3784) (@KarthikReddyPuli)
|
||||
* fix(router): catch-all conflicting wildcard (#3812) (@FirePing32)
|
||||
* fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) (@chncaption)
|
||||
* fix(tree): correctly expand the capacity of params (#3502) (@georgijd-form3)
|
||||
* fix(uri): query binding bug (#3236) (@illiafox)
|
||||
* fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss)
|
||||
* fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* chore(CI): update release args (#3595) (@qloog)
|
||||
* chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab)
|
||||
* chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez)
|
||||
* chore(deps): update dependencies to latest versions (#3835) (@appleboy)
|
||||
* chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat)
|
||||
* chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme)
|
||||
* chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538)
|
||||
* chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538)
|
||||
* chore(refactor): modify interface check way (#3855) (@demoManito)
|
||||
* chore(request): check reader if it's nil before reading (#3419) (@noahyao1024)
|
||||
* chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz)
|
||||
* chore: refactor CI and update dependencies (#3848) (@appleboy)
|
||||
* chore: refactor configuration files for better readability (#3951) (@appleboy)
|
||||
* chore: update GitHub Actions configuration (#3792) (@appleboy)
|
||||
* chore: update changelog categories and improve documentation (#3917) (@appleboy)
|
||||
* chore: update dependencies to latest versions (#3694) (@appleboy)
|
||||
* chore: update external dependencies to latest versions (#3950) (@appleboy)
|
||||
* chore: update various Go dependencies to latest versions (#3901) (@appleboy)
|
||||
|
||||
### Build process updates
|
||||
|
||||
* build(codecov): Added a codecov configuration (#3891) (@flc1125)
|
||||
* ci(Makefile): vet command add .PHONY (#3915) (@imalasong)
|
||||
* ci(lint): update tooling and workflows for consistency (#3834) (@appleboy)
|
||||
* ci(release): refactor changelog regex patterns and exclusions (#3914) (@appleboy)
|
||||
* ci(testing): add go1.22 version (#3842) (@appleboy)
|
||||
|
||||
### Documentation updates
|
||||
|
||||
* docs(context): Added deprecation comments to BindWith (#3880) (@flc1125)
|
||||
* docs(middleware): comments to function `BasicAuthForProxy` (#3881) (@EndlessParadox1)
|
||||
* docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) (@EndlessParadox1)
|
||||
* docs: fix typo in comment (#3868) (@testwill)
|
||||
* docs: fix typo in function documentation (#3872) (@TotomiEcio)
|
||||
* docs: remove redundant comments (#3765) (@WeiTheShinobi)
|
||||
* feat: update version constant to v1.10.0 (#3952) (@appleboy)
|
||||
|
||||
### Others
|
||||
|
||||
* Upgrade golang.org/x/net -> v0.13.0 (#3684) (@cpcf)
|
||||
* test(git): gitignore add develop tools (#3370) (@demoManito)
|
||||
* test(http): use constant instead of numeric literal (#3863) (@testwill)
|
||||
* test(path): Optimize unit test execution results (#3883) (@flc1125)
|
||||
* test(render): increased unit tests coverage (#3691) (@araujo88)
|
||||
|
||||
## Gin v1.9.1
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512)
|
||||
|
||||
### SECURITY
|
||||
|
||||
* fix lack of escaping of filename in Content-Disposition [#3556](https://github.com/gin-gonic/gin/pull/3556)
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* refactor: use bytes.ReplaceAll directly [#3455](https://github.com/gin-gonic/gin/pull/3455)
|
||||
* convert strings and slices using the officially recommended way [#3344](https://github.com/gin-gonic/gin/pull/3344)
|
||||
* improve render code coverage [#3525](https://github.com/gin-gonic/gin/pull/3525)
|
||||
|
||||
### DOCS
|
||||
|
||||
* docs: changed documentation link for trusted proxies [#3575](https://github.com/gin-gonic/gin/pull/3575)
|
||||
* chore: improve linting, testing, and GitHub Actions setup [#3583](https://github.com/gin-gonic/gin/pull/3583)
|
||||
|
||||
## Gin v1.9.0
|
||||
|
||||
### BREAK CHANGES
|
||||
|
||||
* Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150)
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460)
|
||||
* fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397)
|
||||
* Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503)
|
||||
|
||||
### SECURITY
|
||||
|
||||
* Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333)
|
||||
* fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505)
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184)
|
||||
* chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316)
|
||||
* fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327)
|
||||
* remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395)
|
||||
* refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433)
|
||||
* console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453)
|
||||
* chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456)
|
||||
* chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464)
|
||||
* chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489)
|
||||
|
||||
### DOCS
|
||||
|
||||
* docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260)
|
||||
* docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400)
|
||||
* docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449)
|
||||
* docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446)
|
||||
|
||||
## Gin v1.8.2
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227)))
|
||||
* fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803)))
|
||||
|
||||
### SECURITY
|
||||
|
||||
* Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432)))
|
||||
|
||||
## 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)
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* 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
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* 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
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
|
||||
|
||||
## Gin v1.7.4
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* bump new release to fix checksum mismatch
|
||||
|
||||
## Gin v1.7.3
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* 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
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* 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
|
||||
### BUG FIXES
|
||||
|
||||
* 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
|
||||
### BUG FIXES
|
||||
|
||||
* 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))
|
||||
@ -23,7 +255,7 @@
|
||||
* 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))
|
||||
* remove an 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))
|
||||
@ -47,33 +279,44 @@
|
||||
|
||||
## Gin v1.6.2
|
||||
|
||||
### BUGFIXES
|
||||
### BUG FIXES
|
||||
|
||||
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
||||
|
||||
## Gin v1.6.1
|
||||
|
||||
### BUGFIXES
|
||||
### BUG FIXES
|
||||
|
||||
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
||||
|
||||
## Gin v1.6.0
|
||||
|
||||
### BREAKING
|
||||
|
||||
* chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)
|
||||
* drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
|
||||
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
||||
|
||||
### FEATURES
|
||||
|
||||
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
||||
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
||||
### BUGFIXES
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
* Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
|
||||
* Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228)
|
||||
* fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216)
|
||||
* Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166)
|
||||
* [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121)
|
||||
* Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)
|
||||
* tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229)
|
||||
* tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222)
|
||||
@ -88,7 +331,9 @@
|
||||
* upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149)
|
||||
* Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970)
|
||||
* Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)
|
||||
|
||||
### DOCS
|
||||
|
||||
* docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)
|
||||
* Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217)
|
||||
* Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202)
|
||||
@ -101,7 +346,9 @@
|
||||
* Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165)
|
||||
* docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153)
|
||||
* Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)
|
||||
|
||||
### MISC
|
||||
|
||||
* ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)
|
||||
* chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231)
|
||||
* Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)
|
||||
@ -241,7 +488,7 @@
|
||||
- [FIX] Refactor render
|
||||
- [FIX] Reworked tests
|
||||
- [FIX] logger now supports cygwin
|
||||
- [FIX] Use X-Forwarded-For before X-Real-Ip
|
||||
- [FIX] Use X-Forwarded-For before X-Real-IP
|
||||
- [FIX] time.Time binding (#904)
|
||||
|
||||
## Gin 1.1.4
|
||||
|
@ -8,6 +8,6 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- 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.
|
||||
- If your pull request contains a new feature, please document it on the README.
|
||||
|
55
Makefile
55
Makefile
@ -1,5 +1,6 @@
|
||||
GO ?= go
|
||||
GOFMT ?= gofmt "-s"
|
||||
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
PACKAGES ?= $(shell $(GO) list ./...)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go")
|
||||
@ -7,10 +8,11 @@ TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | gr
|
||||
TESTTAGS ?= ""
|
||||
|
||||
.PHONY: test
|
||||
# Run tests to verify code functionality.
|
||||
test:
|
||||
echo "mode: count" > coverage.out
|
||||
for d in $(TESTFOLDER); do \
|
||||
$(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
|
||||
$(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
|
||||
cat tmp.out; \
|
||||
if grep -q "^--- FAIL" tmp.out; then \
|
||||
rm tmp.out; \
|
||||
@ -29,10 +31,12 @@ test:
|
||||
done
|
||||
|
||||
.PHONY: fmt
|
||||
# Ensure consistent code formatting.
|
||||
fmt:
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
# format (check only).
|
||||
fmt-check:
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
@ -41,31 +45,62 @@ fmt-check:
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: vet
|
||||
# Examine packages and report suspicious constructs if any.
|
||||
vet:
|
||||
$(GO) vet $(VETPACKAGES)
|
||||
|
||||
.PHONY: lint
|
||||
# Inspect source code for stylistic errors or potential bugs.
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u golang.org/x/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
misspell -error $(GOFILES)
|
||||
|
||||
.PHONY: misspell
|
||||
# Correct commonly misspelled English words in source code.
|
||||
misspell:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
misspell -w $(GOFILES)
|
||||
|
||||
.PHONY: misspell-check
|
||||
# misspell (check only).
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
misspell -error $(GOFILES)
|
||||
|
||||
.PHONY: tools
|
||||
# Install tools (golint and misspell).
|
||||
tools:
|
||||
go install golang.org/x/lint/golint; \
|
||||
go install github.com/client9/misspell/cmd/misspell;
|
||||
@if [ $(GO_VERSION) -gt 15 ]; then \
|
||||
$(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
|
||||
|
||||
.PHONY: help
|
||||
# Help.
|
||||
help:
|
||||
@echo ''
|
||||
@echo 'Usage:'
|
||||
@echo ' make [target]'
|
||||
@echo ''
|
||||
@echo 'Targets:'
|
||||
@awk '/^[a-zA-Z\-\0-9]+:/ { \
|
||||
helpMessage = match(lastLine, /^# (.*)/); \
|
||||
if (helpMessage) { \
|
||||
helpCommand = substr($$1, 0, index($$1, ":")-1); \
|
||||
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
|
||||
printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \
|
||||
} \
|
||||
} \
|
||||
{ lastLine = $$0 }' $(MAKEFILE_LIST)
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
29
auth.go
29
auth.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -16,6 +16,9 @@ import (
|
||||
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||
const AuthUserKey = "user"
|
||||
|
||||
// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.
|
||||
const AuthProxyUserKey = "proxy_user"
|
||||
|
||||
// Accounts defines a key/value for user/pass list of authorized logins.
|
||||
type Accounts map[string]string
|
||||
|
||||
@ -31,7 +34,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
for _, pair := range a {
|
||||
if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
|
||||
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
|
||||
return pair.user, true
|
||||
}
|
||||
}
|
||||
@ -89,3 +92,25 @@ func authorizationHeader(user, password string) string {
|
||||
base := user + ":" + password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
||||
}
|
||||
|
||||
// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware.
|
||||
// If the realm is empty, "Proxy Authorization Required" will be used by default.
|
||||
func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc {
|
||||
if realm == "" {
|
||||
realm = "Proxy Authorization Required"
|
||||
}
|
||||
realm = "Basic realm=" + strconv.Quote(realm)
|
||||
pairs := processAccounts(accounts)
|
||||
return func(c *Context) {
|
||||
proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization"))
|
||||
if !found {
|
||||
// Credentials doesn't match, we return 407 and abort handlers chain.
|
||||
c.Header("Proxy-Authenticate", realm)
|
||||
c.AbortWithStatus(http.StatusProxyAuthRequired)
|
||||
return
|
||||
}
|
||||
// The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using
|
||||
// c.MustGet(gin.AuthProxyUserKey).
|
||||
c.Set(AuthProxyUserKey, proxyUser)
|
||||
}
|
||||
}
|
||||
|
45
auth_test.go
45
auth_test.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -90,7 +90,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/login", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
@ -109,7 +109,7 @@ func TestBasicAuth401(t *testing.T) {
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/login", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
@ -129,7 +129,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/login", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
@ -137,3 +137,40 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate"))
|
||||
}
|
||||
|
||||
func TestBasicAuthForProxySucceed(t *testing.T) {
|
||||
accounts := Accounts{"admin": "password"}
|
||||
router := New()
|
||||
router.Use(BasicAuthForProxy(accounts, ""))
|
||||
router.Any("/*proxyPath", func(c *Context) {
|
||||
c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||
req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password"))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "admin", w.Body.String())
|
||||
}
|
||||
|
||||
func TestBasicAuthForProxy407(t *testing.T) {
|
||||
called := false
|
||||
accounts := Accounts{"foo": "bar"}
|
||||
router := New()
|
||||
router.Use(BasicAuthForProxy(accounts, ""))
|
||||
router.Any("/*proxyPath", func(c *Context) {
|
||||
called = true
|
||||
c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called)
|
||||
assert.Equal(t, http.StatusProxyAuthRequired, w.Code)
|
||||
assert.Equal(t, "Basic realm=\"Proxy Authorization Required\"", w.Header().Get("Proxy-Authenticate"))
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -14,21 +14,21 @@ import (
|
||||
func BenchmarkOneRoute(B *testing.B) {
|
||||
router := New()
|
||||
router.GET("/ping", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/ping")
|
||||
runRequest(B, router, http.MethodGet, "/ping")
|
||||
}
|
||||
|
||||
func BenchmarkRecoveryMiddleware(B *testing.B) {
|
||||
router := New()
|
||||
router.Use(Recovery())
|
||||
router.GET("/", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/")
|
||||
runRequest(B, router, http.MethodGet, "/")
|
||||
}
|
||||
|
||||
func BenchmarkLoggerMiddleware(B *testing.B) {
|
||||
router := New()
|
||||
router.Use(LoggerWithWriter(newMockWriter()))
|
||||
router.GET("/", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/")
|
||||
runRequest(B, router, http.MethodGet, "/")
|
||||
}
|
||||
|
||||
func BenchmarkManyHandlers(B *testing.B) {
|
||||
@ -37,7 +37,7 @@ func BenchmarkManyHandlers(B *testing.B) {
|
||||
router.Use(func(c *Context) {})
|
||||
router.Use(func(c *Context) {})
|
||||
router.GET("/ping", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/ping")
|
||||
runRequest(B, router, http.MethodGet, "/ping")
|
||||
}
|
||||
|
||||
func Benchmark5Params(B *testing.B) {
|
||||
@ -45,7 +45,7 @@ func Benchmark5Params(B *testing.B) {
|
||||
router := New()
|
||||
router.Use(func(c *Context) {})
|
||||
router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/param/path/to/parameter/john/12345")
|
||||
runRequest(B, router, http.MethodGet, "/param/path/to/parameter/john/12345")
|
||||
}
|
||||
|
||||
func BenchmarkOneRouteJSON(B *testing.B) {
|
||||
@ -56,7 +56,7 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
||||
router.GET("/json", func(c *Context) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
})
|
||||
runRequest(B, router, "GET", "/json")
|
||||
runRequest(B, router, http.MethodGet, "/json")
|
||||
}
|
||||
|
||||
func BenchmarkOneRouteHTML(B *testing.B) {
|
||||
@ -68,7 +68,7 @@ func BenchmarkOneRouteHTML(B *testing.B) {
|
||||
router.GET("/html", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "index", "hola")
|
||||
})
|
||||
runRequest(B, router, "GET", "/html")
|
||||
runRequest(B, router, http.MethodGet, "/html")
|
||||
}
|
||||
|
||||
func BenchmarkOneRouteSet(B *testing.B) {
|
||||
@ -76,7 +76,7 @@ func BenchmarkOneRouteSet(B *testing.B) {
|
||||
router.GET("/ping", func(c *Context) {
|
||||
c.Set("key", "value")
|
||||
})
|
||||
runRequest(B, router, "GET", "/ping")
|
||||
runRequest(B, router, http.MethodGet, "/ping")
|
||||
}
|
||||
|
||||
func BenchmarkOneRouteString(B *testing.B) {
|
||||
@ -84,13 +84,13 @@ func BenchmarkOneRouteString(B *testing.B) {
|
||||
router.GET("/text", func(c *Context) {
|
||||
c.String(http.StatusOK, "this is a plain text")
|
||||
})
|
||||
runRequest(B, router, "GET", "/text")
|
||||
runRequest(B, router, http.MethodGet, "/text")
|
||||
}
|
||||
|
||||
func BenchmarkManyRoutesFist(B *testing.B) {
|
||||
router := New()
|
||||
router.Any("/ping", func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/ping")
|
||||
runRequest(B, router, http.MethodGet, "/ping")
|
||||
}
|
||||
|
||||
func BenchmarkManyRoutesLast(B *testing.B) {
|
||||
@ -103,7 +103,7 @@ func Benchmark404(B *testing.B) {
|
||||
router := New()
|
||||
router.Any("/something", func(c *Context) {})
|
||||
router.NoRoute(func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/ping")
|
||||
runRequest(B, router, http.MethodGet, "/ping")
|
||||
}
|
||||
|
||||
func Benchmark404Many(B *testing.B) {
|
||||
@ -118,7 +118,7 @@ func Benchmark404Many(B *testing.B) {
|
||||
router.GET("/user/:id/:mode", func(c *Context) {})
|
||||
|
||||
router.NoRoute(func(c *Context) {})
|
||||
runRequest(B, router, "GET", "/viewfake")
|
||||
runRequest(B, router, http.MethodGet, "/viewfake")
|
||||
}
|
||||
|
||||
type mockWriter struct {
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. 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 !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
@ -22,6 +21,8 @@ const (
|
||||
MIMEMSGPACK = "application/x-msgpack"
|
||||
MIMEMSGPACK2 = "application/msgpack"
|
||||
MIMEYAML = "application/x-yaml"
|
||||
MIMEYAML2 = "application/yaml"
|
||||
MIMETOML = "application/toml"
|
||||
)
|
||||
|
||||
// Binding describes the interface which needs to be implemented for binding the
|
||||
@ -29,27 +30,27 @@ const (
|
||||
// the form POST.
|
||||
type Binding interface {
|
||||
Name() string
|
||||
Bind(*http.Request, interface{}) error
|
||||
Bind(*http.Request, any) error
|
||||
}
|
||||
|
||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||
// but it reads the body from supplied bytes instead of req.Body.
|
||||
type BindingBody interface {
|
||||
Binding
|
||||
BindBody([]byte, interface{}) error
|
||||
BindBody([]byte, any) error
|
||||
}
|
||||
|
||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||
// but it read the Params.
|
||||
// but it reads the Params.
|
||||
type BindingUri interface {
|
||||
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
|
||||
// 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
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||
type StructValidator interface {
|
||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||
@ -57,32 +58,34 @@ type StructValidator interface {
|
||||
// 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.
|
||||
// Otherwise nil must be returned.
|
||||
ValidateStruct(interface{}) error
|
||||
ValidateStruct(any) error
|
||||
|
||||
// Engine returns the underlying validator engine which powers the
|
||||
// StructValidator implementation.
|
||||
Engine() interface{}
|
||||
Engine() any
|
||||
}
|
||||
|
||||
// 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.
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
||||
// These implement the Binding interface and can be used to bind the data
|
||||
// present in the request to struct instances.
|
||||
var (
|
||||
JSON = jsonBinding{}
|
||||
XML = xmlBinding{}
|
||||
Form = formBinding{}
|
||||
Query = queryBinding{}
|
||||
FormPost = formPostBinding{}
|
||||
FormMultipart = formMultipartBinding{}
|
||||
ProtoBuf = protobufBinding{}
|
||||
MsgPack = msgpackBinding{}
|
||||
YAML = yamlBinding{}
|
||||
Uri = uriBinding{}
|
||||
Header = headerBinding{}
|
||||
JSON BindingBody = jsonBinding{}
|
||||
XML BindingBody = xmlBinding{}
|
||||
Form Binding = formBinding{}
|
||||
Query Binding = queryBinding{}
|
||||
FormPost Binding = formPostBinding{}
|
||||
FormMultipart Binding = formMultipartBinding{}
|
||||
ProtoBuf BindingBody = protobufBinding{}
|
||||
MsgPack BindingBody = msgpackBinding{}
|
||||
YAML BindingBody = yamlBinding{}
|
||||
Uri BindingUri = uriBinding{}
|
||||
Header Binding = headerBinding{}
|
||||
Plain BindingBody = plainBinding{}
|
||||
TOML BindingBody = tomlBinding{}
|
||||
)
|
||||
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
@ -101,8 +104,10 @@ func Default(method, contentType string) Binding {
|
||||
return ProtoBuf
|
||||
case MIMEMSGPACK, MIMEMSGPACK2:
|
||||
return MsgPack
|
||||
case MIMEYAML:
|
||||
case MIMEYAML, MIMEYAML2:
|
||||
return YAML
|
||||
case MIMETOML:
|
||||
return TOML
|
||||
case MIMEMultipartPOSTForm:
|
||||
return FormMultipart
|
||||
default: // case MIMEPOSTForm:
|
||||
@ -110,7 +115,7 @@ func Default(method, contentType string) Binding {
|
||||
}
|
||||
}
|
||||
|
||||
func validate(obj interface{}) error {
|
||||
func validate(obj any) error {
|
||||
if Validator == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -3,15 +3,16 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
@ -25,7 +26,7 @@ func TestBindingMsgPack(t *testing.T) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
assert.NotNil(t, buf)
|
||||
err := codec.NewEncoder(buf, h).Encode(test)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := buf.Bytes()
|
||||
|
||||
@ -39,20 +40,20 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body,
|
||||
assert.Equal(t, name, b.Name())
|
||||
|
||||
obj := FooStruct{}
|
||||
req := requestWithBody("POST", path, body)
|
||||
req := requestWithBody(http.MethodPost, path, body)
|
||||
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||
err := b.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
|
||||
obj = FooStruct{}
|
||||
req = requestWithBody("POST", badPath, badBody)
|
||||
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||
err = MsgPack.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBindingDefaultMsgPack(t *testing.T) {
|
||||
assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK))
|
||||
assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2))
|
||||
assert.Equal(t, MsgPack, Default(http.MethodPost, MIMEMSGPACK))
|
||||
assert.Equal(t, MsgPack, Default(http.MethodPut, MIMEMSGPACK2))
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build nomsgpack
|
||||
// +build nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
@ -20,6 +19,8 @@ const (
|
||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||
MIMEPROTOBUF = "application/x-protobuf"
|
||||
MIMEYAML = "application/x-yaml"
|
||||
MIMEYAML2 = "application/yaml"
|
||||
MIMETOML = "application/toml"
|
||||
)
|
||||
|
||||
// Binding describes the interface which needs to be implemented for binding the
|
||||
@ -27,42 +28,42 @@ const (
|
||||
// the form POST.
|
||||
type Binding interface {
|
||||
Name() string
|
||||
Bind(*http.Request, interface{}) error
|
||||
Bind(*http.Request, any) error
|
||||
}
|
||||
|
||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||
// but it reads the body from supplied bytes instead of req.Body.
|
||||
type BindingBody interface {
|
||||
Binding
|
||||
BindBody([]byte, interface{}) error
|
||||
BindBody([]byte, any) error
|
||||
}
|
||||
|
||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||
// but it read the Params.
|
||||
// but it reads the Params.
|
||||
type BindingUri interface {
|
||||
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
|
||||
// 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
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||
type StructValidator interface {
|
||||
// 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 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.
|
||||
// Otherwise nil must be returned.
|
||||
ValidateStruct(interface{}) error
|
||||
ValidateStruct(any) error
|
||||
|
||||
// Engine returns the underlying validator engine which powers the
|
||||
// StructValidator implementation.
|
||||
Engine() interface{}
|
||||
Engine() any
|
||||
}
|
||||
|
||||
// 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.
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
||||
@ -79,6 +80,8 @@ var (
|
||||
YAML = yamlBinding{}
|
||||
Uri = uriBinding{}
|
||||
Header = headerBinding{}
|
||||
TOML = tomlBinding{}
|
||||
Plain = plainBinding{}
|
||||
)
|
||||
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
@ -95,16 +98,18 @@ func Default(method, contentType string) Binding {
|
||||
return XML
|
||||
case MIMEPROTOBUF:
|
||||
return ProtoBuf
|
||||
case MIMEYAML:
|
||||
case MIMEYAML, MIMEYAML2:
|
||||
return YAML
|
||||
case MIMEMultipartPOSTForm:
|
||||
return FormMultipart
|
||||
case MIMETOML:
|
||||
return TOML
|
||||
default: // case MIMEPOSTForm:
|
||||
return Form
|
||||
}
|
||||
}
|
||||
|
||||
func validate(obj interface{}) error {
|
||||
func validate(obj any) error {
|
||||
if Validator == nil {
|
||||
return nil
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -18,23 +18,30 @@ type defaultValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
type sliceValidateError []error
|
||||
type SliceValidationError []error
|
||||
|
||||
func (err sliceValidateError) Error() string {
|
||||
var errMsgs []string
|
||||
for i, e := range err {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
|
||||
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
|
||||
func (err SliceValidationError) Error() string {
|
||||
if len(err) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(errMsgs, "\n")
|
||||
|
||||
var b strings.Builder
|
||||
for i := 0; i < len(err); i++ {
|
||||
if err[i] != nil {
|
||||
if b.Len() > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error())
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
var _ StructValidator = &defaultValidator{}
|
||||
var _ StructValidator = (*defaultValidator)(nil)
|
||||
|
||||
// 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 {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
@ -42,12 +49,15 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
value := reflect.ValueOf(obj)
|
||||
switch value.Kind() {
|
||||
case reflect.Ptr:
|
||||
return v.ValidateStruct(value.Elem().Interface())
|
||||
if value.Elem().Kind() != reflect.Struct {
|
||||
return v.ValidateStruct(value.Elem().Interface())
|
||||
}
|
||||
return v.validateStruct(obj)
|
||||
case reflect.Struct:
|
||||
return v.validateStruct(obj)
|
||||
case reflect.Slice, reflect.Array:
|
||||
count := value.Len()
|
||||
validateRet := make(sliceValidateError, 0)
|
||||
validateRet := make(SliceValidationError, 0)
|
||||
for i := 0; i < count; i++ {
|
||||
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||
validateRet = append(validateRet, err)
|
||||
@ -63,7 +73,7 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
}
|
||||
|
||||
// validateStruct receives struct type
|
||||
func (v *defaultValidator) validateStruct(obj interface{}) error {
|
||||
func (v *defaultValidator) validateStruct(obj any) error {
|
||||
v.lazyinit()
|
||||
return v.validate.Struct(obj)
|
||||
}
|
||||
@ -71,8 +81,8 @@ func (v *defaultValidator) validateStruct(obj interface{}) error {
|
||||
// Engine returns the underlying validator engine which powers the default
|
||||
// Validator instance. This is useful if you want to register custom validations
|
||||
// or struct level validations. See validator GoDoc for more info -
|
||||
// https://godoc.org/gopkg.in/go-playground/validator.v8
|
||||
func (v *defaultValidator) Engine() interface{} {
|
||||
// https://pkg.go.dev/github.com/go-playground/validator/v10
|
||||
func (v *defaultValidator) Engine() any {
|
||||
v.lazyinit()
|
||||
return v.validate
|
||||
}
|
||||
|
28
binding/default_validator_benchmark_test.go
Normal file
28
binding/default_validator_benchmark_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
// 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
|
||||
e := make(SliceValidationError, size)
|
||||
for j := 0; j < size; j++ {
|
||||
e[j] = errors.New(strconv.Itoa(j))
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(e.Error()) == 0 {
|
||||
b.Errorf("error")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,18 +9,38 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSliceValidateError(t *testing.T) {
|
||||
func TestSliceValidationError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err sliceValidateError
|
||||
err SliceValidationError
|
||||
want string
|
||||
}{
|
||||
{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
|
||||
{"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("sliceValidateError.Error() = %v, want %v", got, tt.want)
|
||||
t.Errorf("SliceValidationError.Error() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -34,7 +54,7 @@ func TestDefaultValidator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v *defaultValidator
|
||||
obj interface{}
|
||||
obj any
|
||||
wantErr bool
|
||||
}{
|
||||
{"validate nil obj", &defaultValidator{}, nil, false},
|
||||
|
@ -1,10 +1,11 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. 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"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -18,14 +19,12 @@ func (formBinding) Name() string {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
return err
|
||||
}
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
|
||||
return err
|
||||
}
|
||||
if err := mapForm(obj, req.Form); err != nil {
|
||||
return err
|
||||
@ -37,7 +36,7 @@ func (formPostBinding) Name() string {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -51,7 +50,7 @@ func (formMultipartBinding) Name() string {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -7,6 +7,7 @@ package binding
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -16,22 +17,34 @@ import (
|
||||
"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 convert 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")
|
||||
}
|
||||
|
||||
func mapForm(ptr interface{}, form map[string][]string) error {
|
||||
func mapForm(ptr any, form map[string][]string) error {
|
||||
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{}
|
||||
|
||||
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
|
||||
ptrVal := reflect.ValueOf(ptr)
|
||||
var pointed interface{}
|
||||
var pointed any
|
||||
if ptrVal.Kind() == reflect.Ptr {
|
||||
ptrVal = ptrVal.Elem()
|
||||
pointed = ptrVal.Interface()
|
||||
@ -49,7 +62,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
|
||||
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
|
||||
@ -57,11 +70,11 @@ type formSource map[string][]string
|
||||
var _ setter = formSource(nil)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
return err
|
||||
}
|
||||
@ -71,7 +84,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var vKind = value.Kind()
|
||||
vKind := value.Kind()
|
||||
|
||||
if vKind == reflect.Ptr {
|
||||
var isNew bool
|
||||
@ -80,14 +93,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
isNew = true
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
if isNew && isSetted {
|
||||
if isNew && isSet {
|
||||
value.Set(vPtr)
|
||||
}
|
||||
return isSetted, nil
|
||||
return isSet, nil
|
||||
}
|
||||
|
||||
if vKind != reflect.Struct || !field.Anonymous {
|
||||
@ -103,19 +116,19 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
if vKind == reflect.Struct {
|
||||
tValue := value.Type()
|
||||
|
||||
var isSetted bool
|
||||
var isSet bool
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
sf := tValue.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
||||
ok, err := mapping(value.Field(i), sf, setter, tag)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isSetted = isSetted || ok
|
||||
isSet = isSet || ok
|
||||
}
|
||||
return isSetted, nil
|
||||
return isSet, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@ -146,13 +159,70 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
||||
if k, v := head(opt, "="); k == "default" {
|
||||
setOpt.isDefaultExists = true
|
||||
setOpt.defaultValue = v
|
||||
|
||||
// convert semicolon-separated default values to csv-separated values for processing in setByForm
|
||||
if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
|
||||
cfTag := field.Tag.Get("collection_format")
|
||||
if cfTag == "" || cfTag == "multi" || cfTag == "csv" {
|
||||
setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||
type BindUnmarshaler interface {
|
||||
// UnmarshalParam decodes and assigns a value from an form or query param.
|
||||
UnmarshalParam(param string) error
|
||||
}
|
||||
|
||||
// trySetCustom tries to set a custom type value
|
||||
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
|
||||
// to skip the default value setting.
|
||||
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
||||
switch v := value.Addr().Interface().(type) {
|
||||
case BindUnmarshaler:
|
||||
return true, v.UnmarshalParam(val)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
|
||||
cfTag := field.Tag.Get("collection_format")
|
||||
if cfTag == "" || cfTag == "multi" {
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
var sep string
|
||||
switch cfTag {
|
||||
case "csv":
|
||||
sep = ","
|
||||
case "ssv":
|
||||
sep = " "
|
||||
case "tsv":
|
||||
sep = "\t"
|
||||
case "pipes":
|
||||
sep = "|"
|
||||
default:
|
||||
return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag)
|
||||
}
|
||||
|
||||
totalLength := 0
|
||||
for _, v := range vs {
|
||||
totalLength += strings.Count(v, sep) + 1
|
||||
}
|
||||
newVs = make([]string, 0, totalLength)
|
||||
for _, v := range vs {
|
||||
newVs = append(newVs, strings.Split(v, sep)...)
|
||||
}
|
||||
|
||||
return newVs, nil
|
||||
}
|
||||
|
||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||
vs, ok := form[tagValue]
|
||||
if !ok && !opt.isDefaultExists {
|
||||
return false, nil
|
||||
@ -162,15 +232,46 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
||||
case reflect.Slice:
|
||||
if !ok {
|
||||
vs = []string{opt.defaultValue}
|
||||
|
||||
// pre-process the default value for multi if present
|
||||
cfTag := field.Tag.Get("collection_format")
|
||||
if cfTag == "" || cfTag == "multi" {
|
||||
vs = strings.Split(opt.defaultValue, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, err = trySetCustom(vs[0], value); ok {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
if vs, err = trySplit(vs, field); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, setSlice(vs, value, field)
|
||||
case reflect.Array:
|
||||
if !ok {
|
||||
vs = []string{opt.defaultValue}
|
||||
|
||||
// pre-process the default value for multi if present
|
||||
cfTag := field.Tag.Get("collection_format")
|
||||
if cfTag == "" || cfTag == "multi" {
|
||||
vs = strings.Split(opt.defaultValue, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, err = trySetCustom(vs[0], value); ok {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
if vs, err = trySplit(vs, field); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(vs) != value.Len() {
|
||||
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||
}
|
||||
|
||||
return true, setArray(vs, value, field)
|
||||
default:
|
||||
var val string
|
||||
@ -180,6 +281,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
||||
|
||||
if len(vs) > 0 {
|
||||
val = vs[0]
|
||||
if val == "" {
|
||||
val = opt.defaultValue
|
||||
}
|
||||
}
|
||||
if ok, err := trySetCustom(val, value); ok {
|
||||
return ok, err
|
||||
}
|
||||
return true, setWithProperType(val, value, field)
|
||||
}
|
||||
@ -198,7 +305,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||
case reflect.Int64:
|
||||
switch value.Interface().(type) {
|
||||
case time.Duration:
|
||||
return setTimeDuration(val, value, field)
|
||||
return setTimeDuration(val, value)
|
||||
}
|
||||
return setIntField(val, 64, value)
|
||||
case reflect.Uint:
|
||||
@ -223,10 +330,17 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||
switch value.Interface().(type) {
|
||||
case time.Time:
|
||||
return setTimeField(val, field, value)
|
||||
case multipart.FileHeader:
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||
case reflect.Map:
|
||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||
case reflect.Ptr:
|
||||
if !value.Elem().IsValid() {
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
}
|
||||
return setWithProperType(val, value.Elem(), field)
|
||||
default:
|
||||
return errUnknownType
|
||||
}
|
||||
@ -284,21 +398,26 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
||||
}
|
||||
|
||||
switch tf := strings.ToLower(timeFormat); tf {
|
||||
case "unix", "unixnano":
|
||||
case "unix", "unixmilli", "unixmicro", "unixnano":
|
||||
tv, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := time.Duration(1)
|
||||
if tf == "unixnano" {
|
||||
d = time.Second
|
||||
var t time.Time
|
||||
switch tf {
|
||||
case "unix":
|
||||
t = time.Unix(tv, 0)
|
||||
case "unixmilli":
|
||||
t = time.UnixMilli(tv)
|
||||
case "unixmicro":
|
||||
t = time.UnixMicro(tv)
|
||||
default:
|
||||
t = time.Unix(0, tv)
|
||||
}
|
||||
|
||||
t := time.Unix(tv/int64(d), tv%int64(d))
|
||||
value.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
if val == "" {
|
||||
@ -348,7 +467,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -358,20 +477,17 @@ func setTimeDuration(val string, value reflect.Value, field reflect.StructField)
|
||||
}
|
||||
|
||||
func head(str, sep string) (head string, tail string) {
|
||||
idx := strings.Index(str, sep)
|
||||
if idx < 0 {
|
||||
return str, ""
|
||||
}
|
||||
return str[:idx], str[idx+len(sep):]
|
||||
head, tail, _ = strings.Cut(str, sep)
|
||||
return head, tail
|
||||
}
|
||||
|
||||
func setFormMap(ptr interface{}, form map[string][]string) error {
|
||||
func setFormMap(ptr any, form map[string][]string) error {
|
||||
el := reflect.TypeOf(ptr).Elem()
|
||||
|
||||
if el.Kind() == reflect.Slice {
|
||||
ptrMap, ok := ptr.(map[string][]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map slices of strings")
|
||||
return ErrConvertMapStringSlice
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v
|
||||
@ -382,7 +498,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
|
||||
|
||||
ptrMap, ok := ptr.(map[string]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map of strings")
|
||||
return ErrConvertToMapString
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v[len(v)-1] // pick last
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||
// Copyright 2019 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.
|
||||
|
||||
|
@ -5,11 +5,17 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMappingBaseTypes(t *testing.T) {
|
||||
@ -18,9 +24,9 @@ func TestMappingBaseTypes(t *testing.T) {
|
||||
}
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
value interface{}
|
||||
value any
|
||||
form string
|
||||
expect interface{}
|
||||
expect any
|
||||
}{
|
||||
{"base type", struct{ F int }{}, "9", int(9)},
|
||||
{"base type", struct{ F int8 }{}, "9", int8(9)},
|
||||
@ -43,6 +49,7 @@ func TestMappingBaseTypes(t *testing.T) {
|
||||
{"zero value", struct{ F uint }{}, "", uint(0)},
|
||||
{"zero value", struct{ F bool }{}, "", false},
|
||||
{"zero value", struct{ F float32 }{}, "", float32(0)},
|
||||
{"file value", struct{ F *multipart.FileHeader }{}, "", &multipart.FileHeader{}},
|
||||
} {
|
||||
tp := reflect.TypeOf(tt.value)
|
||||
testName := tt.name + ":" + tp.Field(0).Type.String()
|
||||
@ -53,7 +60,7 @@ func TestMappingBaseTypes(t *testing.T) {
|
||||
field := val.Elem().Type().Field(0)
|
||||
|
||||
_, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form")
|
||||
assert.NoError(t, err, testName)
|
||||
require.NoError(t, err, testName)
|
||||
|
||||
actual := val.Elem().Field(0).Interface()
|
||||
assert.Equal(t, tt.expect, actual, testName)
|
||||
@ -62,13 +69,15 @@ func TestMappingBaseTypes(t *testing.T) {
|
||||
|
||||
func TestMappingDefault(t *testing.T) {
|
||||
var s struct {
|
||||
Str string `form:",default=defaultVal"`
|
||||
Int int `form:",default=9"`
|
||||
Slice []int `form:",default=9"`
|
||||
Array [1]int `form:",default=9"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "defaultVal", s.Str)
|
||||
assert.Equal(t, 9, s.Int)
|
||||
assert.Equal(t, []int{9}, s.Slice)
|
||||
assert.Equal(t, [1]int{9}, s.Array)
|
||||
@ -79,7 +88,7 @@ func TestMappingSkipField(t *testing.T) {
|
||||
A int
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, s.A)
|
||||
}
|
||||
@ -90,7 +99,7 @@ func TestMappingIgnoreField(t *testing.T) {
|
||||
B int `form:"-"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 9, s.A)
|
||||
assert.Equal(t, 0, s.B)
|
||||
@ -102,7 +111,7 @@ func TestMappingUnexportedField(t *testing.T) {
|
||||
b int `form:"b"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 9, s.A)
|
||||
assert.Equal(t, 0, s.b)
|
||||
@ -113,8 +122,8 @@ func TestMappingPrivateField(t *testing.T) {
|
||||
f int `form:"field"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"field": {"6"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(0), s.f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, s.f)
|
||||
}
|
||||
|
||||
func TestMappingUnknownFieldType(t *testing.T) {
|
||||
@ -123,7 +132,7 @@ func TestMappingUnknownFieldType(t *testing.T) {
|
||||
}
|
||||
|
||||
err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, errUnknownType, err)
|
||||
}
|
||||
|
||||
@ -131,9 +140,9 @@ func TestMappingURI(t *testing.T) {
|
||||
var s struct {
|
||||
F int `uri:"field"`
|
||||
}
|
||||
err := mapUri(&s, map[string][]string{"field": {"6"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(6), s.F)
|
||||
err := mapURI(&s, map[string][]string{"field": {"6"}})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 6, s.F)
|
||||
}
|
||||
|
||||
func TestMappingForm(t *testing.T) {
|
||||
@ -141,8 +150,35 @@ func TestMappingForm(t *testing.T) {
|
||||
F int `form:"field"`
|
||||
}
|
||||
err := mapForm(&s, map[string][]string{"field": {"6"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(6), s.F)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 6, s.F)
|
||||
}
|
||||
|
||||
func TestMappingFormFieldNotSent(t *testing.T) {
|
||||
var s struct {
|
||||
F string `form:"field,default=defVal"`
|
||||
}
|
||||
err := mapForm(&s, map[string][]string{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "defVal", s.F)
|
||||
}
|
||||
|
||||
func TestMappingFormWithEmptyToDefault(t *testing.T) {
|
||||
var s struct {
|
||||
F string `form:"field,default=DefVal"`
|
||||
}
|
||||
err := mapForm(&s, map[string][]string{"field": {""}})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "DefVal", s.F)
|
||||
}
|
||||
|
||||
func TestMapFormWithTag(t *testing.T) {
|
||||
var s struct {
|
||||
F int `externalTag:"field"`
|
||||
}
|
||||
err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 6, s.F)
|
||||
}
|
||||
|
||||
func TestMappingTime(t *testing.T) {
|
||||
@ -156,7 +192,7 @@ func TestMappingTime(t *testing.T) {
|
||||
|
||||
var err error
|
||||
time.Local, err = time.LoadLocation("Europe/Berlin")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = mapForm(&s, map[string][]string{
|
||||
"Time": {"2019-01-20T16:02:58Z"},
|
||||
@ -165,7 +201,7 @@ func TestMappingTime(t *testing.T) {
|
||||
"CSTTime": {"2019-01-20"},
|
||||
"UTCTime": {"2019-01-20"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String())
|
||||
assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String())
|
||||
@ -180,14 +216,14 @@ func TestMappingTime(t *testing.T) {
|
||||
Time time.Time `time_location:"wrong"`
|
||||
}
|
||||
err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}})
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong time value
|
||||
var wrongTime struct {
|
||||
Time time.Time
|
||||
}
|
||||
err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}})
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMappingTimeDuration(t *testing.T) {
|
||||
@ -197,12 +233,12 @@ func TestMappingTimeDuration(t *testing.T) {
|
||||
|
||||
// ok
|
||||
err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5*time.Second, s.D)
|
||||
|
||||
// error
|
||||
err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMappingSlice(t *testing.T) {
|
||||
@ -212,17 +248,17 @@ func TestMappingSlice(t *testing.T) {
|
||||
|
||||
// default value
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []int{9}, s.Slice)
|
||||
|
||||
// ok
|
||||
err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []int{3, 4}, s.Slice)
|
||||
|
||||
// error
|
||||
err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMappingArray(t *testing.T) {
|
||||
@ -232,20 +268,125 @@ func TestMappingArray(t *testing.T) {
|
||||
|
||||
// wrong default
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
// ok
|
||||
err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, [2]int{3, 4}, s.Array)
|
||||
|
||||
// error - not enough vals
|
||||
err = mappingByPtr(&s, formSource{"array": {"3"}}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
// error - wrong value
|
||||
err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMappingCollectionFormat(t *testing.T) {
|
||||
var s struct {
|
||||
SliceMulti []int `form:"slice_multi" collection_format:"multi"`
|
||||
SliceCsv []int `form:"slice_csv" collection_format:"csv"`
|
||||
SliceSsv []int `form:"slice_ssv" collection_format:"ssv"`
|
||||
SliceTsv []int `form:"slice_tsv" collection_format:"tsv"`
|
||||
SlicePipes []int `form:"slice_pipes" collection_format:"pipes"`
|
||||
ArrayMulti [2]int `form:"array_multi" collection_format:"multi"`
|
||||
ArrayCsv [2]int `form:"array_csv" collection_format:"csv"`
|
||||
ArraySsv [2]int `form:"array_ssv" collection_format:"ssv"`
|
||||
ArrayTsv [2]int `form:"array_tsv" collection_format:"tsv"`
|
||||
ArrayPipes [2]int `form:"array_pipes" collection_format:"pipes"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{
|
||||
"slice_multi": {"1", "2"},
|
||||
"slice_csv": {"1,2"},
|
||||
"slice_ssv": {"1 2"},
|
||||
"slice_tsv": {"1 2"},
|
||||
"slice_pipes": {"1|2"},
|
||||
"array_multi": {"1", "2"},
|
||||
"array_csv": {"1,2"},
|
||||
"array_ssv": {"1 2"},
|
||||
"array_tsv": {"1 2"},
|
||||
"array_pipes": {"1|2"},
|
||||
}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []int{1, 2}, s.SliceMulti)
|
||||
assert.Equal(t, []int{1, 2}, s.SliceCsv)
|
||||
assert.Equal(t, []int{1, 2}, s.SliceSsv)
|
||||
assert.Equal(t, []int{1, 2}, s.SliceTsv)
|
||||
assert.Equal(t, []int{1, 2}, s.SlicePipes)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayMulti)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayCsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArraySsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayTsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
|
||||
}
|
||||
|
||||
func TestMappingCollectionFormatInvalid(t *testing.T) {
|
||||
var s struct {
|
||||
SliceCsv []int `form:"slice_csv" collection_format:"xxx"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{
|
||||
"slice_csv": {"1,2"},
|
||||
}, "form")
|
||||
require.Error(t, err)
|
||||
|
||||
var s2 struct {
|
||||
ArrayCsv [2]int `form:"array_csv" collection_format:"xxx"`
|
||||
}
|
||||
err = mappingByPtr(&s2, formSource{
|
||||
"array_csv": {"1,2"},
|
||||
}, "form")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) {
|
||||
var s struct {
|
||||
SliceMulti []int `form:",default=1;2;3" collection_format:"multi"`
|
||||
SliceCsv []int `form:",default=1;2;3" collection_format:"csv"`
|
||||
SliceSsv []int `form:",default=1 2 3" collection_format:"ssv"`
|
||||
SliceTsv []int `form:",default=1\t2\t3" collection_format:"tsv"`
|
||||
SlicePipes []int `form:",default=1|2|3" collection_format:"pipes"`
|
||||
ArrayMulti [2]int `form:",default=1;2" collection_format:"multi"`
|
||||
ArrayCsv [2]int `form:",default=1;2" collection_format:"csv"`
|
||||
ArraySsv [2]int `form:",default=1 2" collection_format:"ssv"`
|
||||
ArrayTsv [2]int `form:",default=1\t2" collection_format:"tsv"`
|
||||
ArrayPipes [2]int `form:",default=1|2" collection_format:"pipes"`
|
||||
SliceStringMulti []string `form:",default=1;2;3" collection_format:"multi"`
|
||||
SliceStringCsv []string `form:",default=1;2;3" collection_format:"csv"`
|
||||
SliceStringSsv []string `form:",default=1 2 3" collection_format:"ssv"`
|
||||
SliceStringTsv []string `form:",default=1\t2\t3" collection_format:"tsv"`
|
||||
SliceStringPipes []string `form:",default=1|2|3" collection_format:"pipes"`
|
||||
ArrayStringMulti [2]string `form:",default=1;2" collection_format:"multi"`
|
||||
ArrayStringCsv [2]string `form:",default=1;2" collection_format:"csv"`
|
||||
ArrayStringSsv [2]string `form:",default=1 2" collection_format:"ssv"`
|
||||
ArrayStringTsv [2]string `form:",default=1\t2" collection_format:"tsv"`
|
||||
ArrayStringPipes [2]string `form:",default=1|2" collection_format:"pipes"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
|
||||
assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
|
||||
assert.Equal(t, []int{1, 2, 3}, s.SliceSsv)
|
||||
assert.Equal(t, []int{1, 2, 3}, s.SliceTsv)
|
||||
assert.Equal(t, []int{1, 2, 3}, s.SlicePipes)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayMulti)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayCsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArraySsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayTsv)
|
||||
assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringMulti)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringCsv)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringSsv)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringTsv)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringPipes)
|
||||
assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringMulti)
|
||||
assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringCsv)
|
||||
assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringSsv)
|
||||
assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringTsv)
|
||||
assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringPipes)
|
||||
}
|
||||
|
||||
func TestMappingStructField(t *testing.T) {
|
||||
@ -256,17 +397,50 @@ func TestMappingStructField(t *testing.T) {
|
||||
}
|
||||
|
||||
err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 9, s.J.I)
|
||||
}
|
||||
|
||||
func TestMappingPtrField(t *testing.T) {
|
||||
type ptrStruct struct {
|
||||
Key int64 `json:"key"`
|
||||
}
|
||||
|
||||
type ptrRequest struct {
|
||||
Items []*ptrStruct `json:"items" form:"items"`
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// With 0 items.
|
||||
var req0 ptrRequest
|
||||
err = mappingByPtr(&req0, formSource{}, "form")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, req0.Items)
|
||||
|
||||
// With 1 item.
|
||||
var req1 ptrRequest
|
||||
err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, req1.Items, 1)
|
||||
assert.EqualValues(t, 1, req1.Items[0].Key)
|
||||
|
||||
// With 2 items.
|
||||
var req2 ptrRequest
|
||||
err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, req2.Items, 2)
|
||||
assert.EqualValues(t, 1, req2.Items[0].Key)
|
||||
assert.EqualValues(t, 2, req2.Items[1].Key)
|
||||
}
|
||||
|
||||
func TestMappingMapField(t *testing.T) {
|
||||
var s struct {
|
||||
M map[string]int
|
||||
}
|
||||
|
||||
err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, map[string]int{"one": 1}, s.M)
|
||||
}
|
||||
|
||||
@ -277,5 +451,187 @@ func TestMappingIgnoredCircularRef(t *testing.T) {
|
||||
var s S
|
||||
|
||||
err := mappingByPtr(&s, formSource{}, "form")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type customUnmarshalParamHex int
|
||||
|
||||
func (f *customUnmarshalParamHex) UnmarshalParam(param string) error {
|
||||
v, err := strconv.ParseInt(param, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = customUnmarshalParamHex(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) {
|
||||
var s struct {
|
||||
Foo customUnmarshalParamHex `form:"foo"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, 245, s.Foo)
|
||||
}
|
||||
|
||||
func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) {
|
||||
var s struct {
|
||||
Foo customUnmarshalParamHex `uri:"foo"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, 245, s.Foo)
|
||||
}
|
||||
|
||||
type customUnmarshalParamType struct {
|
||||
Protocol string
|
||||
Path string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
||||
parts := strings.Split(param, ":")
|
||||
if len(parts) != 3 {
|
||||
return errors.New("invalid format")
|
||||
}
|
||||
f.Protocol = parts[0]
|
||||
f.Path = parts[1]
|
||||
f.Name = parts[2]
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMappingCustomStructTypeWithFormTag(t *testing.T) {
|
||||
var s struct {
|
||||
FileData customUnmarshalParamType `form:"data"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||
}
|
||||
|
||||
func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
||||
var s struct {
|
||||
FileData customUnmarshalParamType `uri:"data"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||
}
|
||||
|
||||
func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
||||
var s struct {
|
||||
FileData *customUnmarshalParamType `form:"data"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||
}
|
||||
|
||||
func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
||||
var s struct {
|
||||
FileData *customUnmarshalParamType `uri:"data"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||
}
|
||||
|
||||
type customPath []string
|
||||
|
||||
func (p *customPath) UnmarshalParam(param string) error {
|
||||
elems := strings.Split(param, "/")
|
||||
n := len(elems)
|
||||
if n < 2 {
|
||||
return errors.New("invalid format")
|
||||
}
|
||||
|
||||
*p = elems
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMappingCustomSliceUri(t *testing.T) {
|
||||
var s struct {
|
||||
FileData customPath `uri:"path"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "bar", s.FileData[0])
|
||||
assert.EqualValues(t, "foo", s.FileData[1])
|
||||
}
|
||||
|
||||
func TestMappingCustomSliceForm(t *testing.T) {
|
||||
var s struct {
|
||||
FileData customPath `form:"path"`
|
||||
}
|
||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "bar", s.FileData[0])
|
||||
assert.EqualValues(t, "foo", s.FileData[1])
|
||||
}
|
||||
|
||||
type objectID [12]byte
|
||||
|
||||
func (o *objectID) UnmarshalParam(param string) error {
|
||||
oid, err := convertTo(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = oid
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertTo(s string) (objectID, error) {
|
||||
var nilObjectID objectID
|
||||
if len(s) != 24 {
|
||||
return nilObjectID, errors.New("invalid format")
|
||||
}
|
||||
|
||||
var oid [12]byte
|
||||
_, err := hex.Decode(oid[:], []byte(s))
|
||||
if err != nil {
|
||||
return nilObjectID, err
|
||||
}
|
||||
|
||||
return oid, nil
|
||||
}
|
||||
|
||||
func TestMappingCustomArrayUri(t *testing.T) {
|
||||
var s struct {
|
||||
FileData objectID `uri:"id"`
|
||||
}
|
||||
val := `664a062ac74a8ad104e0e80f`
|
||||
err := mappingByPtr(&s, formSource{"id": {val}}, "uri")
|
||||
require.NoError(t, err)
|
||||
|
||||
expected, _ := convertTo(val)
|
||||
assert.EqualValues(t, expected, s.FileData)
|
||||
}
|
||||
|
||||
func TestMappingCustomArrayForm(t *testing.T) {
|
||||
var s struct {
|
||||
FileData objectID `form:"id"`
|
||||
}
|
||||
val := `664a062ac74a8ad104e0e80f`
|
||||
err := mappingByPtr(&s, formSource{"id": {val}}, "form")
|
||||
require.NoError(t, err)
|
||||
|
||||
expected, _ := convertTo(val)
|
||||
assert.EqualValues(t, expected, s.FileData)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
import (
|
||||
@ -12,7 +16,7 @@ func (headerBinding) Name() string {
|
||||
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 {
|
||||
return err
|
||||
@ -21,7 +25,7 @@ func (headerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
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")
|
||||
}
|
||||
|
||||
@ -29,6 +33,6 @@ type headerSource map[string][]string
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,7 +6,7 @@ package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
|
||||
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
||||
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
|
||||
// interface{} as a Number instead of as a float64.
|
||||
// any as a Number instead of as a float64.
|
||||
var EnableDecoderUseNumber = false
|
||||
|
||||
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
|
||||
@ -30,18 +30,18 @@ func (jsonBinding) Name() string {
|
||||
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 {
|
||||
return fmt.Errorf("invalid request")
|
||||
return errors.New("invalid request")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func decodeJSON(r io.Reader, obj interface{}) error {
|
||||
func decodeJSON(r io.Reader, obj any) error {
|
||||
decoder := json.NewDecoder(r)
|
||||
if EnableDecoderUseNumber {
|
||||
decoder.UseNumber()
|
||||
|
@ -1,9 +1,8 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
@ -21,15 +20,15 @@ func (msgpackBinding) Name() string {
|
||||
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)
|
||||
}
|
||||
|
||||
func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
|
||||
func (msgpackBinding) BindBody(body []byte, obj any) error {
|
||||
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)
|
||||
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
||||
return err
|
||||
|
@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
@ -26,7 +25,7 @@ func TestMsgpackBindingBindBody(t *testing.T) {
|
||||
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
|
||||
h := &codec.MsgpackHandle{}
|
||||
err := codec.NewEncoder(&bs, h).Encode(obj)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||
// Copyright 2019 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.
|
||||
|
||||
@ -15,8 +15,16 @@ type multipartRequest http.Request
|
||||
|
||||
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
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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() {
|
||||
case reflect.Ptr:
|
||||
switch value.Interface().(type) {
|
||||
@ -40,26 +48,26 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
|
||||
}
|
||||
case reflect.Slice:
|
||||
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
|
||||
isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||
if err != nil || !isSetted {
|
||||
return isSetted, err
|
||||
isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||
if err != nil || !isSet {
|
||||
return isSet, err
|
||||
}
|
||||
value.Set(slice)
|
||||
return true, nil
|
||||
case reflect.Array:
|
||||
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) {
|
||||
return false, errors.New("unsupported len of array for []*multipart.FileHeader")
|
||||
return false, ErrMultiFileHeaderLenInvalid
|
||||
}
|
||||
for i := range files {
|
||||
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||
if err != nil || !setted {
|
||||
return setted, err
|
||||
set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||
if err != nil || !set {
|
||||
return set, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||
// Copyright 2019 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.
|
||||
|
||||
@ -6,12 +6,13 @@ package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFormMultipartBindingBindOneFile(t *testing.T) {
|
||||
@ -27,7 +28,7 @@ func TestFormMultipartBindingBindOneFile(t *testing.T) {
|
||||
|
||||
req := createRequestMultipartFiles(t, file)
|
||||
err := FormMultipart.Bind(req, &s)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertMultipartFileHeader(t, &s.FileValue, file)
|
||||
assertMultipartFileHeader(t, s.FilePtr, file)
|
||||
@ -53,7 +54,7 @@ func TestFormMultipartBindingBindTwoFiles(t *testing.T) {
|
||||
|
||||
req := createRequestMultipartFiles(t, files...)
|
||||
err := FormMultipart.Bind(req, &s)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, s.SliceValues, len(files))
|
||||
assert.Len(t, s.SlicePtrs, len(files))
|
||||
@ -76,7 +77,7 @@ func TestFormMultipartBindingBindError(t *testing.T) {
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
s interface{}
|
||||
s any
|
||||
}{
|
||||
{"wrong type", &struct {
|
||||
Files int `form:"file"`
|
||||
@ -90,7 +91,7 @@ func TestFormMultipartBindingBindError(t *testing.T) {
|
||||
} {
|
||||
req := createRequestMultipartFiles(t, files...)
|
||||
err := FormMultipart.Bind(req, tt.s)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,17 +107,17 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
|
||||
mw := multipart.NewWriter(&body)
|
||||
for _, file := range files {
|
||||
fw, err := mw.CreateFormFile(file.Fieldname, file.Filename)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err := fw.Write(file.Content)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(file.Content), n)
|
||||
}
|
||||
err := mw.Close()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest("POST", "/", &body)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodPost, "/", &body)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary())
|
||||
return req
|
||||
@ -124,15 +125,15 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
|
||||
|
||||
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
|
||||
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()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := ioutil.ReadAll(fl)
|
||||
assert.NoError(t, err)
|
||||
body, err := io.ReadAll(fl)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(file.Content), string(body))
|
||||
|
||||
err = fl.Close()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
56
binding/plain.go
Normal file
56
binding/plain.go
Normal file
@ -0,0 +1,56 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
)
|
||||
|
||||
type plainBinding struct{}
|
||||
|
||||
func (plainBinding) Name() string {
|
||||
return "plain"
|
||||
}
|
||||
|
||||
func (plainBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
all, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decodePlain(all, obj)
|
||||
}
|
||||
|
||||
func (plainBinding) BindBody(body []byte, obj any) error {
|
||||
return decodePlain(body, obj)
|
||||
}
|
||||
|
||||
func decodePlain(data []byte, obj any) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(obj)
|
||||
|
||||
for v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.String {
|
||||
v.SetString(bytesconv.BytesToString(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := v.Interface().([]byte); ok {
|
||||
v.SetBytes(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("type (%T) unknown type", v)
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. 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 (
|
||||
"io/ioutil"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type protobufBinding struct{}
|
||||
@ -17,16 +18,20 @@ func (protobufBinding) Name() string {
|
||||
return "protobuf"
|
||||
}
|
||||
|
||||
func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
buf, err := ioutil.ReadAll(req.Body)
|
||||
func (b protobufBinding) Bind(req *http.Request, obj any) error {
|
||||
buf, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.BindBody(buf, obj)
|
||||
}
|
||||
|
||||
func (protobufBinding) BindBody(body []byte, obj interface{}) error {
|
||||
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
|
||||
func (protobufBinding) BindBody(body []byte, obj any) error {
|
||||
msg, ok := obj.(proto.Message)
|
||||
if !ok {
|
||||
return errors.New("obj is not ProtoMessage")
|
||||
}
|
||||
if err := proto.Unmarshal(body, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
// Here it's same to return validate(obj), but util now we can't add
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -12,7 +12,7 @@ func (queryBinding) Name() string {
|
||||
return "query"
|
||||
}
|
||||
|
||||
func (queryBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
func (queryBinding) Bind(req *http.Request, obj any) error {
|
||||
values := req.URL.Query()
|
||||
if err := mapForm(obj, values); err != nil {
|
||||
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 (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)
|
||||
}
|
||||
|
||||
func decodeToml(r io.Reader, obj any) error {
|
||||
decoder := toml.NewDecoder(r)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return validate(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)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Copyright 2018 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.
|
||||
|
||||
@ -10,8 +10,8 @@ func (uriBinding) Name() string {
|
||||
return "uri"
|
||||
}
|
||||
|
||||
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
|
||||
if err := mapUri(obj, m); err != nil {
|
||||
func (uriBinding) BindUri(m map[string][]string, obj any) error {
|
||||
if err := mapURI(obj, m); err != nil {
|
||||
return err
|
||||
}
|
||||
return validate(obj)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testInterface interface {
|
||||
@ -59,7 +60,7 @@ type structNoValidationValues struct {
|
||||
StructSlice []substructNoValidation
|
||||
InterfaceSlice []testInterface
|
||||
|
||||
UniversalInterface interface{}
|
||||
UniversalInterface any
|
||||
CustomInterface testInterface
|
||||
|
||||
FloatMap map[string]float32
|
||||
@ -113,10 +114,10 @@ func TestValidateNoValidationValues(t *testing.T) {
|
||||
test := createNoValidationValues()
|
||||
empty := structNoValidationValues{}
|
||||
|
||||
assert.Nil(t, validate(test))
|
||||
assert.Nil(t, validate(&test))
|
||||
assert.Nil(t, validate(empty))
|
||||
assert.Nil(t, validate(&empty))
|
||||
require.NoError(t, validate(test))
|
||||
require.NoError(t, validate(&test))
|
||||
require.NoError(t, validate(empty))
|
||||
require.NoError(t, validate(&empty))
|
||||
|
||||
assert.Equal(t, origin, test)
|
||||
}
|
||||
@ -163,35 +164,59 @@ func TestValidateNoValidationPointers(t *testing.T) {
|
||||
|
||||
//assert.Nil(t, validate(test))
|
||||
//assert.Nil(t, validate(&test))
|
||||
assert.Nil(t, validate(empty))
|
||||
assert.Nil(t, validate(&empty))
|
||||
require.NoError(t, validate(empty))
|
||||
require.NoError(t, validate(&empty))
|
||||
|
||||
//assert.Equal(t, origin, test)
|
||||
}
|
||||
|
||||
type Object map[string]interface{}
|
||||
type Object map[string]any
|
||||
|
||||
func TestValidatePrimitives(t *testing.T) {
|
||||
obj := Object{"foo": "bar", "bar": 1}
|
||||
assert.NoError(t, validate(obj))
|
||||
assert.NoError(t, validate(&obj))
|
||||
require.NoError(t, validate(obj))
|
||||
require.NoError(t, validate(&obj))
|
||||
assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj)
|
||||
|
||||
obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}}
|
||||
assert.NoError(t, validate(obj2))
|
||||
assert.NoError(t, validate(&obj2))
|
||||
require.NoError(t, validate(obj2))
|
||||
require.NoError(t, validate(&obj2))
|
||||
|
||||
nu := 10
|
||||
assert.NoError(t, validate(nu))
|
||||
assert.NoError(t, validate(&nu))
|
||||
require.NoError(t, validate(nu))
|
||||
require.NoError(t, validate(&nu))
|
||||
assert.Equal(t, 10, nu)
|
||||
|
||||
str := "value"
|
||||
assert.NoError(t, validate(str))
|
||||
assert.NoError(t, validate(&str))
|
||||
require.NoError(t, validate(str))
|
||||
require.NoError(t, validate(&str))
|
||||
assert.Equal(t, "value", str)
|
||||
}
|
||||
|
||||
type structModifyValidation struct {
|
||||
Integer int
|
||||
}
|
||||
|
||||
func toZero(sl validator.StructLevel) {
|
||||
var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation)
|
||||
s.Integer = 0
|
||||
}
|
||||
|
||||
func TestValidateAndModifyStruct(t *testing.T) {
|
||||
// This validates that pointers to structs are passed to the validator
|
||||
// giving us the ability to modify the struct being validated.
|
||||
engine, ok := Validator.Engine().(*validator.Validate)
|
||||
assert.True(t, ok)
|
||||
|
||||
engine.RegisterStructValidation(toZero, structModifyValidation{})
|
||||
|
||||
s := structModifyValidation{Integer: 1}
|
||||
errs := validate(&s)
|
||||
|
||||
require.NoError(t, errs)
|
||||
assert.Equal(t, structModifyValidation{Integer: 0}, s)
|
||||
}
|
||||
|
||||
// structCustomValidation is a helper struct we use to check that
|
||||
// custom validation can be registered on it.
|
||||
// The `notone` binding directive is for custom validation and registered later.
|
||||
@ -215,14 +240,14 @@ func TestValidatorEngine(t *testing.T) {
|
||||
|
||||
err := engine.RegisterValidation("notone", notOne)
|
||||
// Check that we can register custom validation without error
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an instance which will fail validation
|
||||
withOne := structCustomValidation{Integer: 1}
|
||||
errs := validate(withOne)
|
||||
|
||||
// Check that we got back non-nil errs
|
||||
assert.NotNil(t, errs)
|
||||
require.Error(t, errs)
|
||||
// Check that the error matches expectation
|
||||
assert.Error(t, errs, "", "", "notone")
|
||||
require.Error(t, errs, "", "", "notone")
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -17,14 +17,14 @@ func (xmlBinding) Name() string {
|
||||
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)
|
||||
}
|
||||
|
||||
func (xmlBinding) BindBody(body []byte, obj interface{}) error {
|
||||
func (xmlBinding) BindBody(body []byte, obj any) error {
|
||||
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)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
return err
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Copyright 2018 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.
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type yamlBinding struct{}
|
||||
@ -18,15 +18,15 @@ func (yamlBinding) Name() string {
|
||||
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)
|
||||
}
|
||||
|
||||
func (yamlBinding) BindBody(body []byte, obj interface{}) error {
|
||||
func (yamlBinding) BindBody(body []byte, obj any) error {
|
||||
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)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
return err
|
||||
|
14
codecov.yml
14
codecov.yml
@ -1,5 +1,13 @@
|
||||
coverage:
|
||||
notify:
|
||||
gitter:
|
||||
require_ci_to_pass: true
|
||||
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
|
||||
target: 99%
|
||||
threshold: 99%
|
||||
|
||||
patch:
|
||||
default:
|
||||
target: 99%
|
||||
threshold: 95%
|
706
context.go
706
context.go
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,11 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build appengine
|
||||
// +build appengine
|
||||
|
||||
package gin
|
||||
|
||||
func init() {
|
||||
defaultAppEngine = true
|
||||
defaultPlatform = PlatformGoogleAppEngine
|
||||
}
|
||||
|
1529
context_test.go
1529
context_test.go
File diff suppressed because it is too large
Load Diff
39
debug.go
39
debug.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -10,19 +10,23 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const ginSupportMinGoVer = 12
|
||||
const ginSupportMinGoVer = 21
|
||||
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||
func IsDebugging() bool {
|
||||
return ginMode == debugCode
|
||||
return atomic.LoadInt32(&ginMode) == debugCode
|
||||
}
|
||||
|
||||
// DebugPrintRouteFunc indicates debug log output format.
|
||||
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
||||
|
||||
// DebugPrintFunc indicates debug log output format.
|
||||
var DebugPrintFunc func(format string, values ...interface{})
|
||||
|
||||
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||
if IsDebugging() {
|
||||
nuHandlers := len(handlers)
|
||||
@ -47,13 +51,20 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
|
||||
}
|
||||
}
|
||||
|
||||
func debugPrint(format string, values ...interface{}) {
|
||||
if IsDebugging() {
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||
func debugPrint(format string, values ...any) {
|
||||
if !IsDebugging() {
|
||||
return
|
||||
}
|
||||
|
||||
if DebugPrintFunc != nil {
|
||||
DebugPrintFunc(format, values...)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||
}
|
||||
|
||||
func getMinVer(v string) (uint64, error) {
|
||||
@ -66,8 +77,8 @@ func getMinVer(v string) (uint64, error) {
|
||||
}
|
||||
|
||||
func debugPrintWARNINGDefault() {
|
||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.12+.
|
||||
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.23+.
|
||||
|
||||
`)
|
||||
}
|
||||
@ -95,9 +106,7 @@ at initialization. ie. before any route is registered or the router is listening
|
||||
}
|
||||
|
||||
func debugPrintError(err error) {
|
||||
if err != nil {
|
||||
if IsDebugging() {
|
||||
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||
}
|
||||
if err != nil && IsDebugging() {
|
||||
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,29 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TODO
|
||||
// func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||
// func debugPrint(format string, values ...interface{}) {
|
||||
// func debugPrint(format string, values ...any) {
|
||||
|
||||
func TestIsDebugging(t *testing.T) {
|
||||
SetMode(DebugMode)
|
||||
@ -59,7 +61,7 @@ func TestDebugPrintError(t *testing.T) {
|
||||
func TestDebugPrintRoutes(t *testing.T) {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||
debugPrintRoute(http.MethodGet, "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||
SetMode(TestMode)
|
||||
})
|
||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||
@ -71,7 +73,7 @@ func TestDebugPrintRouteFunc(t *testing.T) {
|
||||
}
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||
debugPrintRoute(http.MethodGet, "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||
SetMode(TestMode)
|
||||
})
|
||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||
@ -103,8 +105,8 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
})
|
||||
m, e := getMinVer(runtime.Version())
|
||||
if e == nil && m <= ginSupportMinGoVer {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.12+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
if e == nil && m < ginSupportMinGoVer {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
} else {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
}
|
||||
@ -138,7 +140,7 @@ func captureOutput(t *testing.T, f func()) string {
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
var buf strings.Builder
|
||||
wg.Done()
|
||||
_, err := io.Copy(&buf, reader)
|
||||
assert.NoError(t, err)
|
||||
@ -154,13 +156,13 @@ func TestGetMinVer(t *testing.T) {
|
||||
var m uint64
|
||||
var e error
|
||||
_, e = getMinVer("go1")
|
||||
assert.NotNil(t, e)
|
||||
require.Error(t, e)
|
||||
m, e = getMinVer("go1.1")
|
||||
assert.Equal(t, uint64(1), m)
|
||||
assert.Nil(t, e)
|
||||
require.NoError(t, e)
|
||||
m, e = getMinVer("go1.1.1")
|
||||
assert.Nil(t, e)
|
||||
require.NoError(t, e)
|
||||
assert.Equal(t, uint64(1), m)
|
||||
_, e = getMinVer("go1.1.1.1")
|
||||
assert.NotNil(t, e)
|
||||
require.Error(t, e)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -12,8 +12,10 @@ import (
|
||||
|
||||
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||
// See the binding package.
|
||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
||||
//
|
||||
// Deprecated: Use MustBindWith or ShouldBindWith.
|
||||
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
||||
log.Println(`BindWith(\"any, binding.Binding\") error is going to
|
||||
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
|
||||
ShouldBindWith() if you need to manage the error.`)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -18,7 +18,7 @@ func TestBindWith(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||
c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||
|
||||
var obj struct {
|
||||
Foo string `form:"foo"`
|
||||
|
2439
docs/doc.md
Normal file
2439
docs/doc.md
Normal file
File diff suppressed because it is too large
Load Diff
25
errors.go
25
errors.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -34,12 +34,12 @@ const (
|
||||
type Error struct {
|
||||
Err error
|
||||
Type ErrorType
|
||||
Meta interface{}
|
||||
Meta any
|
||||
}
|
||||
|
||||
type errorMsgs []*Error
|
||||
|
||||
var _ error = &Error{}
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
// SetType sets the error's type.
|
||||
func (msg *Error) SetType(flags ErrorType) *Error {
|
||||
@ -48,13 +48,13 @@ func (msg *Error) SetType(flags ErrorType) *Error {
|
||||
}
|
||||
|
||||
// SetMeta sets the error's meta data.
|
||||
func (msg *Error) SetMeta(data interface{}) *Error {
|
||||
func (msg *Error) SetMeta(data any) *Error {
|
||||
msg.Meta = data
|
||||
return msg
|
||||
}
|
||||
|
||||
// JSON creates a properly formatted JSON
|
||||
func (msg *Error) JSON() interface{} {
|
||||
func (msg *Error) JSON() any {
|
||||
jsonData := H{}
|
||||
if msg.Meta != nil {
|
||||
value := reflect.ValueOf(msg.Meta)
|
||||
@ -122,12 +122,13 @@ func (a errorMsgs) Last() *Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Errors returns an array will all the error messages.
|
||||
// Errors returns an array with all the error messages.
|
||||
// Example:
|
||||
// c.Error(errors.New("first"))
|
||||
// c.Error(errors.New("second"))
|
||||
// c.Error(errors.New("third"))
|
||||
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||
//
|
||||
// c.Error(errors.New("first"))
|
||||
// c.Error(errors.New("second"))
|
||||
// c.Error(errors.New("third"))
|
||||
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||
func (a errorMsgs) Errors() []string {
|
||||
if len(a) == 0 {
|
||||
return nil
|
||||
@ -139,14 +140,14 @@ func (a errorMsgs) Errors() []string {
|
||||
return errorStrings
|
||||
}
|
||||
|
||||
func (a errorMsgs) JSON() interface{} {
|
||||
func (a errorMsgs) JSON() any {
|
||||
switch length := len(a); length {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return a.Last().JSON()
|
||||
default:
|
||||
jsonData := make([]interface{}, length)
|
||||
jsonData := make([]any, length)
|
||||
for i, err := range a {
|
||||
jsonData[i] = err.JSON()
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
//go:build go1.13
|
||||
// +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))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,10 +6,12 @@ package gin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
@ -34,7 +36,7 @@ func TestError(t *testing.T) {
|
||||
jsonBytes, _ := json.Marshal(err)
|
||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||
|
||||
err.SetMeta(H{ // nolint: errcheck
|
||||
err.SetMeta(H{ //nolint: errcheck
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
@ -44,7 +46,7 @@ func TestError(t *testing.T) {
|
||||
"data": "some data",
|
||||
}, err.JSON())
|
||||
|
||||
err.SetMeta(H{ // nolint: errcheck
|
||||
err.SetMeta(H{ //nolint: errcheck
|
||||
"error": "custom error",
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
@ -59,7 +61,7 @@ func TestError(t *testing.T) {
|
||||
status string
|
||||
data string
|
||||
}
|
||||
err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck
|
||||
err.SetMeta(customError{status: "200", data: "other data"}) //nolint: errcheck
|
||||
assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON())
|
||||
}
|
||||
|
||||
@ -85,7 +87,7 @@ Error #02: second
|
||||
Error #03: third
|
||||
Meta: map[status:400]
|
||||
`, errs.String())
|
||||
assert.Equal(t, []interface{}{
|
||||
assert.Equal(t, []any{
|
||||
H{"error": "first"},
|
||||
H{"error": "second", "meta": "some data"},
|
||||
H{"error": "third", "status": "400"},
|
||||
@ -104,3 +106,24 @@ Error #03: third
|
||||
assert.Nil(t, errs.JSON())
|
||||
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 :
|
||||
require.ErrorIs(t, err, innerErr)
|
||||
var testErr TestErr
|
||||
require.ErrorAs(t, err, &testErr)
|
||||
}
|
||||
|
54
fs.go
54
fs.go
@ -1,4 +1,4 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -9,37 +9,43 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type onlyFilesFS struct {
|
||||
fs http.FileSystem
|
||||
// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.
|
||||
type OnlyFilesFS struct {
|
||||
FileSystem http.FileSystem
|
||||
}
|
||||
|
||||
type neuteredReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
|
||||
func (o OnlyFilesFS) Open(name string) (http.File, error) {
|
||||
f, err := o.FileSystem.Open(name)
|
||||
|
||||
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
|
||||
// in router.Static().
|
||||
// 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.
|
||||
func Dir(root string, listDirectory bool) http.FileSystem {
|
||||
fs := http.Dir(root)
|
||||
if listDirectory {
|
||||
return fs
|
||||
}
|
||||
return &onlyFilesFS{fs}
|
||||
}
|
||||
|
||||
// Open conforms to http.Filesystem.
|
||||
func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
||||
f, err := fs.fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return neuteredReaddirFile{f}, nil
|
||||
|
||||
return neutralizedReaddirFile{f}, nil
|
||||
}
|
||||
|
||||
// Readdir overrides the http.File default implementation.
|
||||
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
|
||||
type neutralizedReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
|
||||
// Readdir overrides the http.File default implementation and always returns nil.
|
||||
func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
|
||||
// this disables directory listing
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Dir returns an http.FileSystem that can be used by http.FileServer().
|
||||
// It is used internally in router.Static().
|
||||
// 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.
|
||||
func Dir(root string, listDirectory bool) http.FileSystem {
|
||||
fs := http.Dir(root)
|
||||
|
||||
if listDirectory {
|
||||
return fs
|
||||
}
|
||||
|
||||
return &OnlyFilesFS{FileSystem: fs}
|
||||
}
|
||||
|
72
fs_test.go
Normal file
72
fs_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockFileSystem struct {
|
||||
open func(name string) (http.File, error)
|
||||
}
|
||||
|
||||
func (m *mockFileSystem) Open(name string) (http.File, error) {
|
||||
return m.open(name)
|
||||
}
|
||||
|
||||
func TestOnlyFilesFS_Open(t *testing.T) {
|
||||
var testFile *os.File
|
||||
mockFS := &mockFileSystem{
|
||||
open: func(name string) (http.File, error) {
|
||||
return testFile, nil
|
||||
},
|
||||
}
|
||||
fs := &OnlyFilesFS{FileSystem: mockFS}
|
||||
|
||||
file, err := fs.Open("foo")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testFile, file.(neutralizedReaddirFile).File)
|
||||
}
|
||||
|
||||
func TestOnlyFilesFS_Open_err(t *testing.T) {
|
||||
testError := errors.New("mock")
|
||||
mockFS := &mockFileSystem{
|
||||
open: func(_ string) (http.File, error) {
|
||||
return nil, testError
|
||||
},
|
||||
}
|
||||
fs := &OnlyFilesFS{FileSystem: mockFS}
|
||||
|
||||
file, err := fs.Open("foo")
|
||||
|
||||
require.ErrorIs(t, err, testError)
|
||||
assert.Nil(t, file)
|
||||
}
|
||||
|
||||
func Test_neuteredReaddirFile_Readdir(t *testing.T) {
|
||||
n := neutralizedReaddirFile{}
|
||||
|
||||
res, err := n.Readdir(0)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func TestDir_listDirectory(t *testing.T) {
|
||||
testRoot := "foo"
|
||||
fs := Dir(testRoot, true)
|
||||
|
||||
assert.Equal(t, http.Dir(testRoot), fs)
|
||||
}
|
||||
|
||||
func TestDir(t *testing.T) {
|
||||
testRoot := "foo"
|
||||
fs := Dir(testRoot, false)
|
||||
|
||||
assert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs)
|
||||
}
|
354
gin.go
354
gin.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -11,29 +11,54 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
"github.com/gin-gonic/gin/render"
|
||||
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
const escapedColon = "\\:"
|
||||
const colon = ":"
|
||||
const backslash = "\\"
|
||||
|
||||
var (
|
||||
default404Body = []byte("404 page not found")
|
||||
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},
|
||||
},
|
||||
}
|
||||
|
||||
var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
|
||||
var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
|
||||
// HandlersChain defines a HandlerFunc array.
|
||||
// OptionFunc defines the function to change the default configuration
|
||||
type OptionFunc func(*Engine)
|
||||
|
||||
// HandlersChain defines a HandlerFunc slice.
|
||||
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 {
|
||||
if length := len(c); length > 0 {
|
||||
return c[length-1]
|
||||
@ -49,22 +74,34 @@ type RouteInfo struct {
|
||||
HandlerFunc HandlerFunc
|
||||
}
|
||||
|
||||
// RoutesInfo defines a RouteInfo array.
|
||||
// RoutesInfo defines a RouteInfo slice.
|
||||
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"
|
||||
// PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP
|
||||
PlatformFlyIO = "Fly-Client-IP"
|
||||
)
|
||||
|
||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
type Engine struct {
|
||||
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.
|
||||
// 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
|
||||
// and 307 for all other request methods.
|
||||
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.
|
||||
// First superfluous path elements like ../ or // are removed.
|
||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||
@ -75,7 +112,7 @@ type Engine struct {
|
||||
// RedirectTrailingSlash is independent of this option.
|
||||
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.
|
||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||
// and HTTP status code 405.
|
||||
@ -83,44 +120,50 @@ type Engine struct {
|
||||
// handler.
|
||||
HandleMethodNotAllowed bool
|
||||
|
||||
// If enabled, client IP will be parsed from the request's headers that
|
||||
// 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
|
||||
|
||||
// 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 `(*gin.Engine).TrustedProxies`.
|
||||
RemoteIPHeaders []string
|
||||
|
||||
// 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 []string
|
||||
|
||||
// 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.
|
||||
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
|
||||
|
||||
// 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,
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
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.
|
||||
// See the PR #1817 and issue #1644
|
||||
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
|
||||
secureJSONPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
@ -132,20 +175,22 @@ type Engine struct {
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
maxParams uint16
|
||||
maxSections uint16
|
||||
trustedProxies []string
|
||||
trustedCIDRs []*net.IPNet
|
||||
}
|
||||
|
||||
var _ IRouter = &Engine{}
|
||||
var _ IRouter = (*Engine)(nil)
|
||||
|
||||
// New returns a new blank Engine instance without any middleware attached.
|
||||
// By default the configuration is:
|
||||
// By default, the configuration is:
|
||||
// - RedirectTrailingSlash: true
|
||||
// - RedirectFixedPath: false
|
||||
// - HandleMethodNotAllowed: false
|
||||
// - ForwardedByClientIP: true
|
||||
// - UseRawPath: false
|
||||
// - UnescapePathValues: true
|
||||
func New() *Engine {
|
||||
func New(opts ...OptionFunc) *Engine {
|
||||
debugPrintWARNINGNew()
|
||||
engine := &Engine{
|
||||
RouterGroup: RouterGroup{
|
||||
@ -159,8 +204,7 @@ func New() *Engine {
|
||||
HandleMethodNotAllowed: false,
|
||||
ForwardedByClientIP: true,
|
||||
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||
TrustedProxies: []string{"0.0.0.0/0"},
|
||||
AppEngine: defaultAppEngine,
|
||||
TrustedPlatform: defaultPlatform,
|
||||
UseRawPath: false,
|
||||
RemoveExtraSlash: false,
|
||||
UnescapePathValues: true,
|
||||
@ -168,28 +212,40 @@ func New() *Engine {
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJSONPrefix: "while(1);",
|
||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||
trustedCIDRs: defaultTrustedCIDRs,
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
engine.pool.New = func() interface{} {
|
||||
return engine.allocateContext()
|
||||
engine.pool.New = func() any {
|
||||
return engine.allocateContext(engine.maxParams)
|
||||
}
|
||||
return engine
|
||||
return engine.With(opts...)
|
||||
}
|
||||
|
||||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||
func Default() *Engine {
|
||||
func Default(opts ...OptionFunc) *Engine {
|
||||
debugPrintWARNINGDefault()
|
||||
engine := New()
|
||||
engine.Use(Logger(), Recovery())
|
||||
return engine
|
||||
return engine.With(opts...)
|
||||
}
|
||||
|
||||
func (engine *Engine) allocateContext() *Context {
|
||||
v := make(Params, 0, engine.maxParams)
|
||||
return &Context{engine: engine, params: &v}
|
||||
func (engine *Engine) Handler() http.Handler {
|
||||
if !engine.UseH2C {
|
||||
return engine
|
||||
}
|
||||
|
||||
h2s := &http2.Server{}
|
||||
return h2c.NewHandler(engine, h2s)
|
||||
}
|
||||
|
||||
// Delims sets template left and right delims and returns a Engine instance.
|
||||
func (engine *Engine) allocateContext(maxParams uint16) *Context {
|
||||
v := make(Params, 0, 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 {
|
||||
engine.delims = render.Delims{Left: left, Right: right}
|
||||
return engine
|
||||
@ -243,19 +299,19 @@ func (engine *Engine) SetFuncMap(funcMap template.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) {
|
||||
engine.noRoute = handlers
|
||||
engine.rebuild404Handlers()
|
||||
}
|
||||
|
||||
// NoMethod sets the handlers called when... TODO.
|
||||
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
|
||||
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||
engine.noMethod = handlers
|
||||
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...
|
||||
// For example, this is the right place for a logger or error management middleware.
|
||||
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||
@ -265,6 +321,15 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||
return engine
|
||||
}
|
||||
|
||||
// With returns a Engine with the configuration set in the OptionFunc.
|
||||
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
||||
for _, opt := range opts {
|
||||
opt(engine)
|
||||
}
|
||||
|
||||
return engine
|
||||
}
|
||||
|
||||
func (engine *Engine) rebuild404Handlers() {
|
||||
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||
}
|
||||
@ -288,10 +353,13 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
}
|
||||
root.addRoute(path, handlers)
|
||||
|
||||
// Update maxParams
|
||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||
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:
|
||||
@ -320,30 +388,13 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
return routes
|
||||
}
|
||||
|
||||
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.trustedCIDRs = trustedCIDRs
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
err = http.ListenAndServe(address, engine)
|
||||
return
|
||||
}
|
||||
|
||||
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
if engine.TrustedProxies == nil {
|
||||
if engine.trustedProxies == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies))
|
||||
for _, trustedProxy := range engine.TrustedProxies {
|
||||
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
|
||||
for _, trustedProxy := range engine.trustedProxies {
|
||||
if !strings.Contains(trustedProxy, "/") {
|
||||
ip := parseIP(trustedProxy)
|
||||
if ip == nil {
|
||||
@ -366,6 +417,86 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// updateRouteTree do update to the route tree recursively
|
||||
func updateRouteTree(n *node) {
|
||||
n.path = strings.ReplaceAll(n.path, escapedColon, colon)
|
||||
n.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon)
|
||||
n.indices = strings.ReplaceAll(n.indices, backslash, colon)
|
||||
if n.children == nil {
|
||||
return
|
||||
}
|
||||
for _, child := range n.children {
|
||||
updateRouteTree(child)
|
||||
}
|
||||
}
|
||||
|
||||
// updateRouteTrees do update to the route trees
|
||||
func (engine *Engine) updateRouteTrees() {
|
||||
for _, tree := range engine.trees {
|
||||
updateRouteTree(tree.root)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -380,6 +511,23 @@ func parseIP(ip string) net.IP {
|
||||
return parsedIP
|
||||
}
|
||||
|
||||
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
engine.updateRouteTrees()
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
err = http.ListenAndServe(address, engine.Handler())
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
@ -387,17 +535,27 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (engine *Engine) RunUnix(file string) (err error) {
|
||||
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", file)
|
||||
if err != nil {
|
||||
return
|
||||
@ -405,7 +563,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
||||
defer listener.Close()
|
||||
defer os.Remove(file)
|
||||
|
||||
err = http.Serve(listener, engine)
|
||||
err = http.Serve(listener, engine.Handler())
|
||||
return
|
||||
}
|
||||
|
||||
@ -416,6 +574,11 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||
debugPrint("Listening and serving HTTP on fd@%d", fd)
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||
listener, err := net.FileListener(f)
|
||||
if err != nil {
|
||||
@ -426,12 +589,34 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests.
|
||||
// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) {
|
||||
debugPrint("Listening and serving QUIC on %s\n", addr)
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler())
|
||||
return
|
||||
}
|
||||
|
||||
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||
// through the specified net.Listener
|
||||
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
||||
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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http.Serve(listener, engine.Handler())
|
||||
return
|
||||
}
|
||||
|
||||
@ -447,15 +632,17 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
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.
|
||||
// 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) {
|
||||
oldIndexValue := c.index
|
||||
oldHandlers := c.handlers
|
||||
c.reset()
|
||||
engine.handleHTTPRequest(c)
|
||||
|
||||
c.index = oldIndexValue
|
||||
c.handlers = oldHandlers
|
||||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
@ -479,7 +666,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
}
|
||||
root := t[i].root
|
||||
// Find route in tree
|
||||
value := root.getValue(rPath, c.params, unescape)
|
||||
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
|
||||
if value.params != nil {
|
||||
c.Params = *value.params
|
||||
}
|
||||
@ -490,7 +677,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && rPath != "/" {
|
||||
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||
if value.tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
@ -502,18 +689,26 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
break
|
||||
}
|
||||
|
||||
if engine.HandleMethodNotAllowed {
|
||||
if engine.HandleMethodNotAllowed && len(t) > 0 {
|
||||
// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
|
||||
// containing a list of the target resource's currently supported methods.
|
||||
allowed := make([]string, 0, len(t)-1)
|
||||
for _, tree := range engine.trees {
|
||||
if tree.method == httpMethod {
|
||||
continue
|
||||
}
|
||||
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
|
||||
c.handlers = engine.allNoMethod
|
||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||
return
|
||||
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||
allowed = append(allowed, tree.method)
|
||||
}
|
||||
}
|
||||
if len(allowed) > 0 {
|
||||
c.handlers = engine.allNoMethod
|
||||
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
|
||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.handlers = engine.allNoRoute
|
||||
serveError(c, http.StatusNotFound, default404Body)
|
||||
}
|
||||
@ -541,6 +736,9 @@ func redirectTrailingSlash(c *Context) {
|
||||
req := c.Request
|
||||
p := req.URL.Path
|
||||
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
||||
prefix = regSafePrefix.ReplaceAllString(prefix, "")
|
||||
prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
|
||||
|
||||
p = prefix + "/" + req.URL.Path
|
||||
}
|
||||
req.URL.Path = p + "/"
|
||||
|
11
ginS/gins.go
11
ginS/gins.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -37,7 +37,7 @@ func SetHTMLTemplate(templ *template.Template) {
|
||||
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) {
|
||||
engine().NoRoute(handlers...)
|
||||
}
|
||||
@ -108,7 +108,8 @@ func StaticFile(relativePath, filepath string) gin.IRoutes {
|
||||
// of the Router's NotFound handler.
|
||||
// To use the operating system's file system implementation,
|
||||
// use :
|
||||
// router.Static("/static", "/var/www")
|
||||
//
|
||||
// router.Static("/static", "/var/www")
|
||||
func Static(relativePath, root string) gin.IRoutes {
|
||||
return engine().Static(relativePath, root)
|
||||
}
|
||||
@ -118,7 +119,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
||||
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...
|
||||
// For example, this is the right place for a logger or error management middleware.
|
||||
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
||||
@ -145,7 +146,7 @@ func RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
func RunUnix(file string) (err error) {
|
||||
return engine().RunUnix(file)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -9,20 +9,30 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
@ -30,14 +40,27 @@ func testRequest(t *testing.T, url string) {
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
assert.NoError(t, err)
|
||||
resp, err := client.Get(params[0])
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, ioerr := ioutil.ReadAll(resp.Body)
|
||||
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")
|
||||
body, ioerr := io.ReadAll(resp.Body)
|
||||
require.NoError(t, ioerr)
|
||||
|
||||
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) {
|
||||
@ -51,17 +74,85 @@ func TestRunEmpty(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
assert.Error(t, router.Run(":8080"))
|
||||
require.Error(t, router.Run(":8080"))
|
||||
testRequest(t, "http://localhost:8080/example")
|
||||
}
|
||||
|
||||
func TestTrustedCIDRsForRun(t *testing.T) {
|
||||
func TestBadTrustedCIDRs(t *testing.T) {
|
||||
router := New()
|
||||
require.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"))
|
||||
require.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") })
|
||||
require.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")
|
||||
require.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
require.NoError(t, err)
|
||||
socketFile, err := listener.File()
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
require.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")
|
||||
require.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
require.NoError(t, err)
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
require.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"}
|
||||
require.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
}
|
||||
*/
|
||||
|
||||
func TestRunTLS(t *testing.T) {
|
||||
router := New()
|
||||
go func() {
|
||||
@ -74,7 +165,7 @@ func TestRunTLS(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
require.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
testRequest(t, "https://localhost:8443/example")
|
||||
}
|
||||
|
||||
@ -111,7 +202,7 @@ func TestPusher(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
require.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
testRequest(t, "https://localhost:8449/pusher")
|
||||
}
|
||||
|
||||
@ -126,14 +217,14 @@ func TestRunEmptyWithEnv(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
assert.Error(t, router.Run(":3123"))
|
||||
require.Error(t, router.Run(":3123"))
|
||||
testRequest(t, "http://localhost:3123/example")
|
||||
}
|
||||
|
||||
func TestRunTooMuchParams(t *testing.T) {
|
||||
router := New()
|
||||
assert.Panics(t, func() {
|
||||
assert.NoError(t, router.Run("2", "2"))
|
||||
require.NoError(t, router.Run("2", "2"))
|
||||
})
|
||||
}
|
||||
|
||||
@ -147,7 +238,7 @@ func TestRunWithPort(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
assert.Error(t, router.Run(":5150"))
|
||||
require.Error(t, router.Run(":5150"))
|
||||
testRequest(t, "http://localhost:5150/example")
|
||||
}
|
||||
|
||||
@ -167,7 +258,7 @@ func TestUnixSocket(t *testing.T) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("unix", unixTestSocket)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
@ -181,18 +272,43 @@ func TestUnixSocket(t *testing.T) {
|
||||
|
||||
func TestBadUnixSocket(t *testing.T) {
|
||||
router := New()
|
||||
assert.Error(t, router.RunUnix("#/tmp/unix_unit_test"))
|
||||
require.Error(t, router.RunUnix("#/tmp/unix_unit_test"))
|
||||
}
|
||||
|
||||
func TestRunQUIC(t *testing.T) {
|
||||
router := New()
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
|
||||
assert.NoError(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
}()
|
||||
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
require.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
testRequest(t, "https://localhost:8443/example")
|
||||
}
|
||||
|
||||
func TestFileDescriptor(t *testing.T) {
|
||||
router := New()
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
socketFile, err := listener.File()
|
||||
assert.NoError(t, err)
|
||||
if isWindows() {
|
||||
// not supported by windows, it is unimplemented now
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if socketFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
@ -203,7 +319,7 @@ func TestFileDescriptor(t *testing.T) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("tcp", listener.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
@ -217,15 +333,15 @@ func TestFileDescriptor(t *testing.T) {
|
||||
|
||||
func TestBadFileDescriptor(t *testing.T) {
|
||||
router := New()
|
||||
assert.Error(t, router.RunFd(0))
|
||||
require.Error(t, router.RunFd(0))
|
||||
}
|
||||
|
||||
func TestListener(t *testing.T) {
|
||||
router := New()
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.NoError(t, router.RunListener(listener))
|
||||
@ -235,7 +351,7 @@ func TestListener(t *testing.T) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("tcp", listener.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
@ -250,11 +366,11 @@ func TestListener(t *testing.T) {
|
||||
func TestBadListener(t *testing.T) {
|
||||
router := New()
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:10086")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
listener.Close()
|
||||
assert.Error(t, router.RunListener(listener))
|
||||
require.Error(t, router.RunListener(listener))
|
||||
}
|
||||
|
||||
func TestWithHttptestWithAutoSelectedPort(t *testing.T) {
|
||||
@ -280,7 +396,14 @@ func TestConcurrentHandleContext(t *testing.T) {
|
||||
wg.Add(iterations)
|
||||
for i := 0; i < iterations; i++ {
|
||||
go func() {
|
||||
testGetRequestHandler(t, router, "/")
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
||||
assert.Equal(t, 200, w.Code, "should get a 200")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
@ -302,13 +425,177 @@ func TestConcurrentHandleContext(t *testing.T) {
|
||||
// testRequest(t, "http://localhost:8033/example")
|
||||
// }
|
||||
|
||||
func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
assert.NoError(t, err)
|
||||
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") })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
||||
assert.Equal(t, 200, w.Code, "should get a 200")
|
||||
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"
|
||||
}
|
||||
|
||||
func TestEscapedColon(t *testing.T) {
|
||||
router := New()
|
||||
f := func(u string) {
|
||||
router.GET(u, func(c *Context) { c.String(http.StatusOK, u) })
|
||||
}
|
||||
f("/r/r\\:r")
|
||||
f("/r/r:r")
|
||||
f("/r/r/:r")
|
||||
f("/r/r/\\:r")
|
||||
f("/r/r/r\\:r")
|
||||
assert.Panics(t, func() {
|
||||
f("\\foo:")
|
||||
})
|
||||
|
||||
router.updateRouteTrees()
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
testRequest(t, ts.URL+"/r/r123", "", "/r/r:r")
|
||||
testRequest(t, ts.URL+"/r/r:r", "", "/r/r\\:r")
|
||||
testRequest(t, ts.URL+"/r/r/r123", "", "/r/r/:r")
|
||||
testRequest(t, ts.URL+"/r/r/:r", "", "/r/r/\\:r")
|
||||
testRequest(t, ts.URL+"/r/r/r:r", "", "/r/r/r\\:r")
|
||||
}
|
||||
|
340
gin_test.go
340
gin_test.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -8,17 +8,20 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func formatAsDate(t time.Time) string {
|
||||
@ -42,7 +45,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine
|
||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||
})
|
||||
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),
|
||||
})
|
||||
})
|
||||
@ -70,12 +73,50 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
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 {
|
||||
t.Error(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 {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
defer ln.Close()
|
||||
|
||||
url := "http://" + ln.Addr().String() + "/"
|
||||
|
||||
httpClient := 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 := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -90,12 +131,12 @@ func TestLoadHTMLGlobTestMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -110,12 +151,12 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -137,12 +178,12 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
||||
},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := client.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -157,13 +198,13 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/raw")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -188,12 +229,12 @@ func TestLoadHTMLFilesTestMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -208,12 +249,12 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -228,12 +269,12 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -255,12 +296,12 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
||||
},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||
res, err := client.Get(ts.URL + "/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
}
|
||||
|
||||
@ -275,42 +316,42 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
|
||||
res, err := http.Get(ts.URL + "/raw")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
resp, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||
}
|
||||
|
||||
func TestAddRoute(t *testing.T) {
|
||||
router := New()
|
||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}})
|
||||
|
||||
assert.Len(t, router.trees, 1)
|
||||
assert.NotNil(t, router.trees.get("GET"))
|
||||
assert.Nil(t, router.trees.get("POST"))
|
||||
assert.NotNil(t, router.trees.get(http.MethodGet))
|
||||
assert.Nil(t, router.trees.get(http.MethodPost))
|
||||
|
||||
router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
|
||||
|
||||
assert.Len(t, router.trees, 2)
|
||||
assert.NotNil(t, router.trees.get("GET"))
|
||||
assert.NotNil(t, router.trees.get("POST"))
|
||||
assert.NotNil(t, router.trees.get(http.MethodGet))
|
||||
assert.NotNil(t, router.trees.get(http.MethodPost))
|
||||
|
||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||
assert.Len(t, router.trees, 2)
|
||||
}
|
||||
|
||||
func TestAddRouteFails(t *testing.T) {
|
||||
router := New()
|
||||
assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) })
|
||||
assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) })
|
||||
assert.Panics(t, func() { router.addRoute("GET", "/", HandlersChain{}) })
|
||||
assert.Panics(t, func() { router.addRoute(http.MethodGet, "a", HandlersChain{func(_ *Context) {}}) })
|
||||
assert.Panics(t, func() { router.addRoute(http.MethodGet, "/", HandlersChain{}) })
|
||||
|
||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||
assert.Panics(t, func() {
|
||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||
})
|
||||
}
|
||||
|
||||
@ -395,7 +436,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRebuild404Handlers(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
||||
@ -429,7 +469,7 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
||||
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)
|
||||
sf2 := reflect.ValueOf(b)
|
||||
if sf1.Pointer() != sf2.Pointer() {
|
||||
@ -453,27 +493,27 @@ func TestListOfRoutes(t *testing.T) {
|
||||
|
||||
assert.Len(t, list, 7)
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/favicon.ico",
|
||||
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
|
||||
})
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
|
||||
})
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/users/",
|
||||
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$",
|
||||
})
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/users/:id",
|
||||
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$",
|
||||
})
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "POST",
|
||||
Method: http.MethodPost,
|
||||
Path: "/users/:id",
|
||||
Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$",
|
||||
})
|
||||
@ -491,7 +531,7 @@ func TestEngineHandleContext(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
w := performRequest(r, "GET", "/")
|
||||
w := PerformRequest(r, http.MethodGet, "/")
|
||||
assert.Equal(t, 301, w.Code)
|
||||
})
|
||||
}
|
||||
@ -508,10 +548,10 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
||||
r.GET("/:count", func(c *Context) {
|
||||
countStr := c.Param("count")
|
||||
count, err := strconv.Atoi(countStr)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err := c.Writer.Write([]byte("."))
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, n)
|
||||
|
||||
switch {
|
||||
@ -524,7 +564,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
||||
w := PerformRequest(r, http.MethodGet, "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, expectValue, w.Body.Len())
|
||||
})
|
||||
@ -533,87 +573,110 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
||||
assert.Equal(t, int64(expectValue), middlewareCounter)
|
||||
}
|
||||
|
||||
func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) {
|
||||
// given
|
||||
var handlerCounterV1, handlerCounterV2, middlewareCounterV1 int64
|
||||
|
||||
r := New()
|
||||
v1 := r.Group("/v1")
|
||||
{
|
||||
v1.Use(func(c *Context) {
|
||||
atomic.AddInt64(&middlewareCounterV1, 1)
|
||||
})
|
||||
v1.GET("/test", func(c *Context) {
|
||||
atomic.AddInt64(&handlerCounterV1, 1)
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
v2 := r.Group("/v2")
|
||||
{
|
||||
v2.GET("/test", func(c *Context) {
|
||||
c.Request.URL.Path = "/v1/test"
|
||||
r.HandleContext(c)
|
||||
}, func(c *Context) {
|
||||
atomic.AddInt64(&handlerCounterV2, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
responseV1 := PerformRequest(r, "GET", "/v1/test")
|
||||
responseV2 := PerformRequest(r, "GET", "/v2/test")
|
||||
|
||||
// then
|
||||
assert.Equal(t, 200, responseV1.Code)
|
||||
assert.Equal(t, 200, responseV2.Code)
|
||||
assert.Equal(t, int64(2), handlerCounterV1)
|
||||
assert.Equal(t, int64(2), middlewareCounterV1)
|
||||
assert.Equal(t, int64(1), handlerCounterV2)
|
||||
}
|
||||
|
||||
func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
// valid ipv4 cidr
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")}
|
||||
r.TrustedProxies = []string{"0.0.0.0/0"}
|
||||
err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv4 cidr
|
||||
{
|
||||
r.TrustedProxies = []string{"192.168.1.33/33"}
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.33/33"})
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// valid ipv4 address
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")}
|
||||
r.TrustedProxies = []string{"192.168.1.33"}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.33"})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv4 address
|
||||
{
|
||||
r.TrustedProxies = []string{"192.168.1.256"}
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.256"})
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// valid ipv6 address
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
|
||||
r.TrustedProxies = []string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}
|
||||
err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv6 address
|
||||
{
|
||||
r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}
|
||||
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// valid ipv6 cidr
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")}
|
||||
r.TrustedProxies = []string{"::/0"}
|
||||
err := r.SetTrustedProxies([]string{"::/0"})
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv6 cidr
|
||||
{
|
||||
r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}
|
||||
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"})
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// valid combination
|
||||
@ -623,39 +686,34 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
parseCIDR("192.168.0.0/16"),
|
||||
parseCIDR("172.16.0.1/32"),
|
||||
}
|
||||
r.TrustedProxies = []string{
|
||||
err := r.SetTrustedProxies([]string{
|
||||
"::/0",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.1",
|
||||
}
|
||||
})
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid combination
|
||||
{
|
||||
r.TrustedProxies = []string{
|
||||
err := r.SetTrustedProxies([]string{
|
||||
"::/0",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.256",
|
||||
}
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
})
|
||||
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// nil value
|
||||
{
|
||||
r.TrustedProxies = nil
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies(nil)
|
||||
|
||||
assert.Nil(t, trustedCIDRs)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, r.trustedCIDRs)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func parseCIDR(cidr string) *net.IPNet {
|
||||
@ -678,3 +736,71 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo)
|
||||
|
||||
func handlerTest1(c *Context) {}
|
||||
func handlerTest2(c *Context) {}
|
||||
|
||||
func TestNewOptionFunc(t *testing.T) {
|
||||
var fc = func(e *Engine) {
|
||||
e.GET("/test1", handlerTest1)
|
||||
e.GET("/test2", handlerTest2)
|
||||
|
||||
e.Use(func(c *Context) {
|
||||
c.Next()
|
||||
})
|
||||
}
|
||||
|
||||
r := New(fc)
|
||||
|
||||
routes := r.Routes()
|
||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||
}
|
||||
|
||||
func TestWithOptionFunc(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
r.With(func(e *Engine) {
|
||||
e.GET("/test1", handlerTest1)
|
||||
e.GET("/test2", handlerTest2)
|
||||
|
||||
e.Use(func(c *Context) {
|
||||
c.Next()
|
||||
})
|
||||
})
|
||||
|
||||
routes := r.Routes()
|
||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||
}
|
||||
|
||||
type Birthday string
|
||||
|
||||
func (b *Birthday) UnmarshalParam(param string) error {
|
||||
*b = Birthday(strings.Replace(param, "-", "/", -1))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCustomUnmarshalStruct(t *testing.T) {
|
||||
route := Default()
|
||||
var request struct {
|
||||
Birthday Birthday `form:"birthday"`
|
||||
}
|
||||
route.GET("/test", func(ctx *Context) {
|
||||
_ = ctx.BindQuery(&request)
|
||||
ctx.JSON(200, request.Birthday)
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "/test?birthday=2000-01-01", nil)
|
||||
w := httptest.NewRecorder()
|
||||
route.ServeHTTP(w, req)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `"2000/01/01"`, w.Body.String())
|
||||
}
|
||||
|
||||
// Test the fix for https://github.com/gin-gonic/gin/issues/4002
|
||||
func TestMethodNotAllowedNoRoute(t *testing.T) {
|
||||
g := New()
|
||||
g.HandleMethodNotAllowed = true
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
assert.NotPanics(t, func() { g.ServeHTTP(resp, req) })
|
||||
assert.Equal(t, http.StatusNotFound, resp.Code)
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type route struct {
|
||||
@ -295,14 +297,14 @@ func TestShouldBindUri(t *testing.T) {
|
||||
}
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.ShouldBindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
assert.True(t, "" != person.ID)
|
||||
require.NoError(t, c.ShouldBindUri(&person))
|
||||
assert.NotEqual(t, "", person.Name)
|
||||
assert.NotEqual(t, "", person.ID)
|
||||
c.String(http.StatusOK, "ShouldBindUri test OK")
|
||||
})
|
||||
|
||||
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, http.StatusOK, w.Code)
|
||||
}
|
||||
@ -317,14 +319,14 @@ func TestBindUri(t *testing.T) {
|
||||
}
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.BindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
assert.True(t, "" != person.ID)
|
||||
require.NoError(t, c.BindUri(&person))
|
||||
assert.NotEqual(t, "", person.Name)
|
||||
assert.NotEqual(t, "", person.ID)
|
||||
c.String(http.StatusOK, "BindUri test OK")
|
||||
})
|
||||
|
||||
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, http.StatusOK, w.Code)
|
||||
}
|
||||
@ -338,11 +340,11 @@ func TestBindUriError(t *testing.T) {
|
||||
}
|
||||
router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) {
|
||||
var m Member
|
||||
assert.Error(t, c.BindUri(&m))
|
||||
require.Error(t, c.BindUri(&m))
|
||||
})
|
||||
|
||||
path1, _ := exampleFromPath("/new/rest/:num")
|
||||
w1 := performRequest(router, http.MethodGet, path1)
|
||||
w1 := PerformRequest(router, http.MethodGet, path1)
|
||||
assert.Equal(t, http.StatusBadRequest, w1.Code)
|
||||
}
|
||||
|
||||
@ -358,7 +360,7 @@ func TestRaceContextCopy(t *testing.T) {
|
||||
go readWriteKeys(c.Copy())
|
||||
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())
|
||||
}
|
||||
|
||||
@ -389,7 +391,7 @@ func TestGithubAPI(t *testing.T) {
|
||||
|
||||
for _, route := range githubAPI {
|
||||
path, values := exampleFromPath(route.path)
|
||||
w := performRequest(router, route.method, path)
|
||||
w := PerformRequest(router, route.method, path)
|
||||
|
||||
// TEST
|
||||
assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
|
||||
@ -401,7 +403,7 @@ func TestGithubAPI(t *testing.T) {
|
||||
}
|
||||
|
||||
func exampleFromPath(path string) (string, Params) {
|
||||
output := new(bytes.Buffer)
|
||||
output := new(strings.Builder)
|
||||
params := make(Params, 0, 6)
|
||||
start := -1
|
||||
for i, c := range path {
|
||||
@ -410,7 +412,7 @@ func exampleFromPath(path string) (string, Params) {
|
||||
}
|
||||
if start >= 0 {
|
||||
if c == '/' {
|
||||
value := fmt.Sprint(rand.Intn(100000))
|
||||
value := strconv.Itoa(rand.Intn(100000))
|
||||
params = append(params, Param{
|
||||
Key: path[start:i],
|
||||
Value: value,
|
||||
@ -424,7 +426,7 @@ func exampleFromPath(path string) (string, Params) {
|
||||
}
|
||||
}
|
||||
if start >= 0 {
|
||||
value := fmt.Sprint(rand.Intn(100000))
|
||||
value := strconv.Itoa(rand.Intn(100000))
|
||||
params = append(params, Param{
|
||||
Key: path[start:],
|
||||
Value: value,
|
||||
|
48
go.mod
48
go.mod
@ -1,14 +1,46 @@
|
||||
module github.com/gin-gonic/gin
|
||||
|
||||
go 1.13
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.13.1
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
github.com/go-playground/validator/v10 v10.4.1
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go/codec v1.1.7
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
github.com/go-playground/validator/v10 v10.22.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/quic-go/quic-go v0.48.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/ugorji/go/codec v1.2.12
|
||||
golang.org/x/net v0.37.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
)
|
||||
|
138
go.sum
138
go.sum
@ -1,52 +1,114 @@
|
||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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/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/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Gin Core Team. All rights reserved.
|
||||
// Copyright 2023 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.
|
||||
|
||||
@ -9,16 +9,13 @@ import (
|
||||
)
|
||||
|
||||
// StringToBytes converts string to byte slice without a memory allocation.
|
||||
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
|
||||
func StringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
||||
|
||||
// BytesToString converts byte slice to string without a memory allocation.
|
||||
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
|
||||
func BytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
||||
|
22
internal/json/go_json.go
Normal file
22
internal/json/go_json.go
Normal file
@ -0,0 +1,22 @@
|
||||
// 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
|
||||
|
||||
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
|
||||
)
|
@ -1,9 +1,8 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// 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 !jsoniter
|
||||
// +build !jsoniter
|
||||
//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
|
||||
|
||||
package json
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// 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 jsoniter
|
||||
// +build jsoniter
|
||||
|
||||
package json
|
||||
|
||||
|
23
internal/json/sonic.go
Normal file
23
internal/json/sonic.go
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 sonic && avx && (linux || windows || darwin) && amd64
|
||||
|
||||
package json
|
||||
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
var (
|
||||
json = sonic.ConfigStd
|
||||
// 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
|
||||
)
|
80
logger.go
80
logger.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -44,11 +44,18 @@ type LoggerConfig struct {
|
||||
// Optional. Default value is gin.DefaultWriter.
|
||||
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.
|
||||
SkipPaths []string
|
||||
|
||||
// Skip is a Skipper that indicates which logs should not be written.
|
||||
// Optional.
|
||||
Skip Skipper
|
||||
}
|
||||
|
||||
// Skipper is a function to skip logs based on provided Context
|
||||
type Skipper func(c *Context) bool
|
||||
|
||||
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
||||
type LogFormatter func(params LogFormatterParams) string
|
||||
|
||||
@ -70,12 +77,12 @@ type LogFormatterParams struct {
|
||||
Path string
|
||||
// ErrorMessage is set if error has occurred in processing the request.
|
||||
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
|
||||
// BodySize is the size of the Response Body
|
||||
BodySize int
|
||||
// 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.
|
||||
@ -83,6 +90,8 @@ func (p *LogFormatterParams) StatusCodeColor() string {
|
||||
code := p.StatusCode
|
||||
|
||||
switch {
|
||||
case code >= http.StatusContinue && code < http.StatusOK:
|
||||
return white
|
||||
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||
return green
|
||||
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||
@ -138,8 +147,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
|
||||
}
|
||||
|
||||
if param.Latency > time.Minute {
|
||||
// Truncate in a golang < 1.8 safe way
|
||||
param.Latency = param.Latency - param.Latency%time.Second
|
||||
param.Latency = param.Latency.Truncate(time.Second)
|
||||
}
|
||||
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"),
|
||||
@ -162,12 +170,12 @@ func ForceConsoleColor() {
|
||||
consoleColorMode = forceColor
|
||||
}
|
||||
|
||||
// ErrorLogger returns a handlerfunc for any error type.
|
||||
// ErrorLogger returns a HandlerFunc for any error type.
|
||||
func ErrorLogger() HandlerFunc {
|
||||
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 {
|
||||
return func(c *Context) {
|
||||
c.Next()
|
||||
@ -179,7 +187,7 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return LoggerWithConfig(LoggerConfig{})
|
||||
}
|
||||
@ -240,32 +248,34 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
||||
// Process request
|
||||
c.Next()
|
||||
|
||||
// Log only when path is not being skipped
|
||||
if _, ok := skip[path]; !ok {
|
||||
param := LogFormatterParams{
|
||||
Request: c.Request,
|
||||
isTerm: isTerm,
|
||||
Keys: c.Keys,
|
||||
}
|
||||
|
||||
// Stop timer
|
||||
param.TimeStamp = time.Now()
|
||||
param.Latency = param.TimeStamp.Sub(start)
|
||||
|
||||
param.ClientIP = c.ClientIP()
|
||||
param.Method = c.Request.Method
|
||||
param.StatusCode = c.Writer.Status()
|
||||
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
param.BodySize = c.Writer.Size()
|
||||
|
||||
if raw != "" {
|
||||
path = path + "?" + raw
|
||||
}
|
||||
|
||||
param.Path = path
|
||||
|
||||
fmt.Fprint(out, formatter(param))
|
||||
// Log only when it is not being skipped
|
||||
if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {
|
||||
return
|
||||
}
|
||||
|
||||
param := LogFormatterParams{
|
||||
Request: c.Request,
|
||||
isTerm: isTerm,
|
||||
Keys: c.Keys,
|
||||
}
|
||||
|
||||
// Stop timer
|
||||
param.TimeStamp = time.Now()
|
||||
param.Latency = param.TimeStamp.Sub(start)
|
||||
|
||||
param.ClientIP = c.ClientIP()
|
||||
param.Method = c.Request.Method
|
||||
param.StatusCode = c.Writer.Status()
|
||||
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
param.BodySize = c.Writer.Size()
|
||||
|
||||
if raw != "" {
|
||||
path = path + "?" + raw
|
||||
}
|
||||
|
||||
param.Path = path
|
||||
|
||||
fmt.Fprint(out, formatter(param))
|
||||
}
|
||||
}
|
||||
|
152
logger_test.go
152
logger_test.go
@ -1,14 +1,14 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -20,7 +20,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(LoggerWithWriter(buffer))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
@ -31,9 +31,9 @@ func TestLogger(t *testing.T) {
|
||||
router.HEAD("/example", func(c *Context) {})
|
||||
router.OPTIONS("/example", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
@ -41,50 +41,50 @@ func TestLogger(t *testing.T) {
|
||||
// like integration tests because they test the whole logging process rather
|
||||
// than individual functions. Im not sure where these should go.
|
||||
buffer.Reset()
|
||||
performRequest(router, "POST", "/example")
|
||||
PerformRequest(router, http.MethodPost, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "POST")
|
||||
assert.Contains(t, buffer.String(), http.MethodPost)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PUT", "/example")
|
||||
PerformRequest(router, http.MethodPut, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PUT")
|
||||
assert.Contains(t, buffer.String(), http.MethodPut)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "DELETE", "/example")
|
||||
PerformRequest(router, http.MethodDelete, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "DELETE")
|
||||
assert.Contains(t, buffer.String(), http.MethodDelete)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PATCH", "/example")
|
||||
PerformRequest(router, "PATCH", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PATCH")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "HEAD", "/example")
|
||||
PerformRequest(router, "HEAD", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "HEAD")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "OPTIONS", "/example")
|
||||
PerformRequest(router, "OPTIONS", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/notfound")
|
||||
PerformRequest(router, http.MethodGet, "/notfound")
|
||||
assert.Contains(t, buffer.String(), "404")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/notfound")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfig(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{Output: buffer}))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
@ -95,9 +95,9 @@ func TestLoggerWithConfig(t *testing.T) {
|
||||
router.HEAD("/example", func(c *Context) {})
|
||||
router.OPTIONS("/example", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
@ -105,50 +105,50 @@ func TestLoggerWithConfig(t *testing.T) {
|
||||
// like integration tests because they test the whole logging process rather
|
||||
// than individual functions. Im not sure where these should go.
|
||||
buffer.Reset()
|
||||
performRequest(router, "POST", "/example")
|
||||
PerformRequest(router, http.MethodPost, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "POST")
|
||||
assert.Contains(t, buffer.String(), http.MethodPost)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PUT", "/example")
|
||||
PerformRequest(router, http.MethodPut, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PUT")
|
||||
assert.Contains(t, buffer.String(), http.MethodPut)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "DELETE", "/example")
|
||||
PerformRequest(router, http.MethodDelete, "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "DELETE")
|
||||
assert.Contains(t, buffer.String(), http.MethodDelete)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PATCH", "/example")
|
||||
PerformRequest(router, "PATCH", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PATCH")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "HEAD", "/example")
|
||||
PerformRequest(router, "HEAD", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "HEAD")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "OPTIONS", "/example")
|
||||
PerformRequest(router, "OPTIONS", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/notfound")
|
||||
PerformRequest(router, http.MethodGet, "/notfound")
|
||||
assert.Contains(t, buffer.String(), "404")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/notfound")
|
||||
}
|
||||
|
||||
func TestLoggerWithFormatter(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
|
||||
d := DefaultWriter
|
||||
DefaultWriter = buffer
|
||||
@ -169,20 +169,20 @@ func TestLoggerWithFormatter(t *testing.T) {
|
||||
)
|
||||
}))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||
|
||||
// output test
|
||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||
var gotParam LogFormatterParams
|
||||
var gotKeys map[string]interface{}
|
||||
buffer := new(bytes.Buffer)
|
||||
var gotKeys map[string]any
|
||||
buffer := new(strings.Builder)
|
||||
|
||||
router := New()
|
||||
router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs()
|
||||
@ -208,13 +208,14 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||
// set dummy ClientIP
|
||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||
gotKeys = c.Keys
|
||||
time.Sleep(time.Millisecond)
|
||||
})
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||
|
||||
// output test
|
||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
@ -224,11 +225,10 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||
assert.Equal(t, 200, gotParam.StatusCode)
|
||||
assert.NotEmpty(t, gotParam.Latency)
|
||||
assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
|
||||
assert.Equal(t, "GET", gotParam.Method)
|
||||
assert.Equal(t, http.MethodGet, gotParam.Method)
|
||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||
assert.Empty(t, gotParam.ErrorMessage)
|
||||
assert.Equal(t, gotKeys, gotParam.Keys)
|
||||
|
||||
}
|
||||
|
||||
func TestDefaultLogFormatter(t *testing.T) {
|
||||
@ -239,7 +239,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
||||
StatusCode: 200,
|
||||
Latency: time.Second * 5,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
isTerm: false,
|
||||
@ -250,7 +250,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
||||
StatusCode: 200,
|
||||
Latency: time.Second * 5,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
isTerm: true,
|
||||
@ -260,7 +260,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
||||
StatusCode: 200,
|
||||
Latency: time.Millisecond * 9876543210,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
isTerm: true,
|
||||
@ -271,7 +271,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
||||
StatusCode: 200,
|
||||
Latency: time.Millisecond * 9876543210,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
isTerm: false,
|
||||
@ -282,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| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
|
||||
|
||||
}
|
||||
|
||||
func TestColorForMethod(t *testing.T) {
|
||||
@ -293,10 +292,10 @@ func TestColorForMethod(t *testing.T) {
|
||||
return p.MethodColor()
|
||||
}
|
||||
|
||||
assert.Equal(t, blue, colorForMethod("GET"), "get should be blue")
|
||||
assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan")
|
||||
assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow")
|
||||
assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red")
|
||||
assert.Equal(t, blue, colorForMethod(http.MethodGet), "get should be blue")
|
||||
assert.Equal(t, cyan, colorForMethod(http.MethodPost), "post should be cyan")
|
||||
assert.Equal(t, yellow, colorForMethod(http.MethodPut), "put should be yellow")
|
||||
assert.Equal(t, red, colorForMethod(http.MethodDelete), "delete should be red")
|
||||
assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
|
||||
assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
|
||||
assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
|
||||
@ -311,6 +310,7 @@ func TestColorForStatus(t *testing.T) {
|
||||
return p.StatusCodeColor()
|
||||
}
|
||||
|
||||
assert.Equal(t, white, colorForStatus(http.StatusContinue), "1xx should be white")
|
||||
assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green")
|
||||
assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
|
||||
assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
|
||||
@ -329,13 +329,13 @@ func TestIsOutputColor(t *testing.T) {
|
||||
}
|
||||
|
||||
consoleColorMode = autoColor
|
||||
assert.Equal(t, true, p.IsOutputColor())
|
||||
assert.True(t, p.IsOutputColor())
|
||||
|
||||
ForceConsoleColor()
|
||||
assert.Equal(t, true, p.IsOutputColor())
|
||||
assert.True(t, p.IsOutputColor())
|
||||
|
||||
DisableConsoleColor()
|
||||
assert.Equal(t, false, p.IsOutputColor())
|
||||
assert.False(t, p.IsOutputColor())
|
||||
|
||||
// test with isTerm flag false.
|
||||
p = LogFormatterParams{
|
||||
@ -343,13 +343,13 @@ func TestIsOutputColor(t *testing.T) {
|
||||
}
|
||||
|
||||
consoleColorMode = autoColor
|
||||
assert.Equal(t, false, p.IsOutputColor())
|
||||
assert.False(t, p.IsOutputColor())
|
||||
|
||||
ForceConsoleColor()
|
||||
assert.Equal(t, true, p.IsOutputColor())
|
||||
assert.True(t, p.IsOutputColor())
|
||||
|
||||
DisableConsoleColor()
|
||||
assert.Equal(t, false, p.IsOutputColor())
|
||||
assert.False(t, p.IsOutputColor())
|
||||
|
||||
// reset console color mode.
|
||||
consoleColorMode = autoColor
|
||||
@ -359,46 +359,46 @@ func TestErrorLogger(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(ErrorLogger())
|
||||
router.GET("/error", func(c *Context) {
|
||||
c.Error(errors.New("this is an error")) // nolint: errcheck
|
||||
c.Error(errors.New("this is an error")) //nolint: errcheck
|
||||
})
|
||||
router.GET("/abort", func(c *Context) {
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) //nolint: errcheck
|
||||
})
|
||||
router.GET("/print", func(c *Context) {
|
||||
c.Error(errors.New("this is an error")) // nolint: errcheck
|
||||
c.Error(errors.New("this is an error")) //nolint: errcheck
|
||||
c.String(http.StatusInternalServerError, "hola!")
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/error")
|
||||
w := PerformRequest(router, http.MethodGet, "/error")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/abort")
|
||||
w = PerformRequest(router, http.MethodGet, "/abort")
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/print")
|
||||
w = PerformRequest(router, http.MethodGet, "/print")
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||
}
|
||||
|
||||
func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(LoggerWithWriter(buffer, "/skipped"))
|
||||
router.GET("/logged", func(c *Context) {})
|
||||
router.GET("/skipped", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/logged")
|
||||
PerformRequest(router, http.MethodGet, "/logged")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/skipped")
|
||||
PerformRequest(router, http.MethodGet, "/skipped")
|
||||
assert.Contains(t, buffer.String(), "")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{
|
||||
Output: buffer,
|
||||
@ -407,11 +407,31 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
||||
router.GET("/logged", func(c *Context) {})
|
||||
router.GET("/skipped", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/logged")
|
||||
PerformRequest(router, http.MethodGet, "/logged")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/skipped")
|
||||
PerformRequest(router, http.MethodGet, "/skipped")
|
||||
assert.Contains(t, buffer.String(), "")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfigSkipper(t *testing.T) {
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{
|
||||
Output: buffer,
|
||||
Skip: func(c *Context) bool {
|
||||
return c.Writer.Status() == http.StatusNoContent
|
||||
},
|
||||
}))
|
||||
router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) })
|
||||
router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) })
|
||||
|
||||
PerformRequest(router, http.MethodGet, "/logged")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
|
||||
buffer.Reset()
|
||||
PerformRequest(router, http.MethodGet, "/skipped")
|
||||
assert.Contains(t, buffer.String(), "")
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
|
||||
signature += " XX "
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
||||
signature += " X "
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
||||
signature += " XX "
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
||||
func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||
signature := ""
|
||||
router := New()
|
||||
|
||||
// NoMethod disabled
|
||||
router.HandleMethodNotAllowed = false
|
||||
|
||||
router.Use(func(c *Context) {
|
||||
signature += "A"
|
||||
c.Next()
|
||||
@ -144,8 +147,9 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||
router.POST("/", func(c *Context) {
|
||||
signature += " XX "
|
||||
})
|
||||
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
@ -171,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
||||
})
|
||||
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
@ -186,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
||||
c.Next()
|
||||
c.AbortWithStatus(http.StatusGone)
|
||||
signature += "B"
|
||||
|
||||
})
|
||||
router.GET("/", func(c *Context) {
|
||||
signature += "C"
|
||||
c.Next()
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusGone, w.Code)
|
||||
@ -208,7 +211,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(func(context *Context) {
|
||||
signature += "A"
|
||||
context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck
|
||||
context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) //nolint: errcheck
|
||||
})
|
||||
router.Use(func(context *Context) {
|
||||
signature += "B"
|
||||
@ -216,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||
signature += "C"
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
@ -243,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
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))
|
||||
|
34
mode.go
34
mode.go
@ -1,12 +1,14 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
@ -34,15 +36,16 @@ const (
|
||||
// Note that both Logger and Recovery provides custom ways to configure their
|
||||
// output io.Writer.
|
||||
// To support coloring in Windows use:
|
||||
// import "github.com/mattn/go-colorable"
|
||||
// gin.DefaultWriter = colorable.NewColorableStdout()
|
||||
//
|
||||
// import "github.com/mattn/go-colorable"
|
||||
// gin.DefaultWriter = colorable.NewColorableStdout()
|
||||
var DefaultWriter io.Writer = os.Stdout
|
||||
|
||||
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
||||
var DefaultErrorWriter io.Writer = os.Stderr
|
||||
|
||||
var ginMode = debugCode
|
||||
var modeName = DebugMode
|
||||
var ginMode int32 = debugCode
|
||||
var modeName atomic.Value
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(EnvGinMode)
|
||||
@ -52,21 +55,24 @@ func init() {
|
||||
// SetMode sets gin mode according to input string.
|
||||
func SetMode(value string) {
|
||||
if value == "" {
|
||||
value = DebugMode
|
||||
if flag.Lookup("test.v") != nil {
|
||||
value = TestMode
|
||||
} else {
|
||||
value = DebugMode
|
||||
}
|
||||
}
|
||||
|
||||
switch value {
|
||||
case DebugMode:
|
||||
ginMode = debugCode
|
||||
case DebugMode, "":
|
||||
atomic.StoreInt32(&ginMode, debugCode)
|
||||
case ReleaseMode:
|
||||
ginMode = releaseCode
|
||||
atomic.StoreInt32(&ginMode, releaseCode)
|
||||
case TestMode:
|
||||
ginMode = testCode
|
||||
atomic.StoreInt32(&ginMode, testCode)
|
||||
default:
|
||||
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||
}
|
||||
|
||||
modeName = value
|
||||
modeName.Store(value)
|
||||
}
|
||||
|
||||
// DisableBindValidation closes the default validator.
|
||||
@ -86,7 +92,7 @@ func EnableJsonDecoderDisallowUnknownFields() {
|
||||
binding.EnableDecoderDisallowUnknownFields = true
|
||||
}
|
||||
|
||||
// Mode returns currently gin mode.
|
||||
// Mode returns current gin mode.
|
||||
func Mode() string {
|
||||
return modeName
|
||||
return modeName.Load().(string)
|
||||
}
|
||||
|
15
mode_test.go
15
mode_test.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
@ -17,24 +18,24 @@ func init() {
|
||||
}
|
||||
|
||||
func TestSetMode(t *testing.T) {
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
os.Unsetenv(EnvGinMode)
|
||||
|
||||
SetMode("")
|
||||
assert.Equal(t, debugCode, ginMode)
|
||||
assert.Equal(t, DebugMode, Mode())
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
|
||||
SetMode(DebugMode)
|
||||
assert.Equal(t, debugCode, ginMode)
|
||||
assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, DebugMode, Mode())
|
||||
|
||||
SetMode(ReleaseMode)
|
||||
assert.Equal(t, releaseCode, ginMode)
|
||||
assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, ReleaseMode, Mode())
|
||||
|
||||
SetMode(TestMode)
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
|
||||
assert.Panics(t, func() { SetMode("unknown") })
|
||||
|
12
path.go
12
path.go
@ -10,12 +10,12 @@ package gin
|
||||
//
|
||||
// The following rules are applied iteratively until no further processing can
|
||||
// be done:
|
||||
// 1. Replace multiple slashes with a single slash.
|
||||
// 2. Eliminate each . path name element (the current directory).
|
||||
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||
// along with the non-.. element that precedes it.
|
||||
// 4. Eliminate .. elements that begin a rooted path:
|
||||
// that is, replace "/.." by "/" at the beginning of a path.
|
||||
// 1. Replace multiple slashes with a single slash.
|
||||
// 2. Eliminate each . path name element (the current directory).
|
||||
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||
// along with the non-.. element that precedes it.
|
||||
// 4. Eliminate .. elements that begin a rooted path:
|
||||
// that is, replace "/.." by "/" at the beginning of a path.
|
||||
//
|
||||
// If the result of this process is an empty string, "/" is returned.
|
||||
func cleanPath(p string) string {
|
||||
|
@ -6,6 +6,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -80,9 +81,13 @@ func TestPathCleanMallocs(t *testing.T) {
|
||||
t.Skip("skipping malloc count in short mode")
|
||||
}
|
||||
|
||||
if runtime.GOMAXPROCS(0) > 1 {
|
||||
t.Skip("skipping malloc count; GOMAXPROCS>1")
|
||||
}
|
||||
|
||||
for _, test := range cleanTests {
|
||||
allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) })
|
||||
assert.EqualValues(t, allocs, 0)
|
||||
assert.InDelta(t, 0, allocs, 0.01)
|
||||
}
|
||||
}
|
||||
|
||||
|
29
recovery.go
29
recovery.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,9 +6,9 @@ package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -27,14 +27,14 @@ var (
|
||||
)
|
||||
|
||||
// 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.
|
||||
func Recovery() HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter)
|
||||
}
|
||||
|
||||
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||
}
|
||||
@ -60,8 +60,11 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
// condition that warrants a panic stack trace.
|
||||
var brokenPipe bool
|
||||
if ne, ok := err.(*net.OpError); ok {
|
||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||
var se *os.SyscallError
|
||||
if errors.As(ne, &se) {
|
||||
seStr := strings.ToLower(se.Error())
|
||||
if strings.Contains(seStr, "broken pipe") ||
|
||||
strings.Contains(seStr, "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
}
|
||||
@ -89,7 +92,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
}
|
||||
if brokenPipe {
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
c.Error(err.(error)) // nolint: errcheck
|
||||
c.Error(err.(error)) //nolint: errcheck
|
||||
c.Abort()
|
||||
} else {
|
||||
handle(c, err)
|
||||
@ -100,7 +103,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHandleRecovery(c *Context, err interface{}) {
|
||||
func defaultHandleRecovery(c *Context, _ any) {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
@ -119,7 +122,7 @@ func stack(skip int) []byte {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||
if file != lastFile {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -153,7 +156,7 @@ func function(pc uintptr) []byte {
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *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
|
||||
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
||||
name = name[lastSlash+1:]
|
||||
@ -161,11 +164,11 @@ func function(pc uintptr) []byte {
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
name = bytes.ReplaceAll(name, centerDot, dot)
|
||||
return name
|
||||
}
|
||||
|
||||
// timeFormat returns a customized time string for logger.
|
||||
func timeFormat(t time.Time) string {
|
||||
timeString := t.Format("2006/01/02 - 15:04:05")
|
||||
return timeString
|
||||
return t.Format("2006/01/02 - 15:04:05")
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -18,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPanicClean(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
password := "my-super-secret-password"
|
||||
router.Use(RecoveryWithWriter(buffer))
|
||||
@ -27,14 +25,14 @@ func TestPanicClean(t *testing.T) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery",
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery",
|
||||
header{
|
||||
Key: "Host",
|
||||
Value: "www.google.com",
|
||||
},
|
||||
header{
|
||||
Key: "Authorization",
|
||||
Value: fmt.Sprintf("Bearer %s", password),
|
||||
Value: "Bearer " + password,
|
||||
},
|
||||
header{
|
||||
Key: "Content-Type",
|
||||
@ -50,14 +48,14 @@ func TestPanicClean(t *testing.T) {
|
||||
|
||||
// TestPanicInHandler assert that panic has been recovered.
|
||||
func TestPanicInHandler(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
router.Use(RecoveryWithWriter(buffer))
|
||||
router.GET("/recovery", func(_ *Context) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Contains(t, buffer.String(), "panic recovered")
|
||||
@ -68,7 +66,7 @@ func TestPanicInHandler(t *testing.T) {
|
||||
// Debug mode prints the request
|
||||
SetMode(DebugMode)
|
||||
// RUN
|
||||
w = performRequest(router, "GET", "/recovery")
|
||||
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
@ -85,21 +83,21 @@ func TestPanicWithAbort(t *testing.T) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
bs := source(nil, 0)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
|
||||
in := [][]byte{
|
||||
[]byte("Hello world."),
|
||||
[]byte("Hi, gin.."),
|
||||
}
|
||||
bs = source(in, 10)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
|
||||
bs = source(in, 1)
|
||||
assert.Equal(t, []byte("Hello world."), bs)
|
||||
@ -107,7 +105,7 @@ func TestSource(t *testing.T) {
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
bs := function(1)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
}
|
||||
|
||||
// TestPanicWithBrokenPipe asserts that recovery specifically handles
|
||||
@ -122,8 +120,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
||||
|
||||
for errno, expectMsg := range expectMsgs {
|
||||
t.Run(expectMsg, func(t *testing.T) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
var buf strings.Builder
|
||||
|
||||
router := New()
|
||||
router.Use(RecoveryWithWriter(&buf))
|
||||
@ -137,7 +134,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
||||
panic(e)
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, expectCode, w.Code)
|
||||
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
||||
@ -146,10 +143,10 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCustomRecoveryWithWriter(t *testing.T) {
|
||||
errBuffer := new(bytes.Buffer)
|
||||
buffer := new(bytes.Buffer)
|
||||
errBuffer := new(strings.Builder)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
handleRecovery := func(c *Context, err interface{}) {
|
||||
handleRecovery := func(c *Context, err any) {
|
||||
errBuffer.WriteString(err.(string))
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
}
|
||||
@ -158,7 +155,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "panic recovered")
|
||||
@ -169,7 +166,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
||||
// Debug mode prints the request
|
||||
SetMode(DebugMode)
|
||||
// RUN
|
||||
w = performRequest(router, "GET", "/recovery")
|
||||
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
@ -180,11 +177,11 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCustomRecovery(t *testing.T) {
|
||||
errBuffer := new(bytes.Buffer)
|
||||
buffer := new(bytes.Buffer)
|
||||
errBuffer := new(strings.Builder)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
DefaultErrorWriter = buffer
|
||||
handleRecovery := func(c *Context, err interface{}) {
|
||||
handleRecovery := func(c *Context, err any) {
|
||||
errBuffer.WriteString(err.(string))
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
}
|
||||
@ -193,7 +190,7 @@ func TestCustomRecovery(t *testing.T) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "panic recovered")
|
||||
@ -204,7 +201,7 @@ func TestCustomRecovery(t *testing.T) {
|
||||
// Debug mode prints the request
|
||||
SetMode(DebugMode)
|
||||
// RUN
|
||||
w = performRequest(router, "GET", "/recovery")
|
||||
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
@ -215,11 +212,11 @@ func TestCustomRecovery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
||||
errBuffer := new(bytes.Buffer)
|
||||
buffer := new(bytes.Buffer)
|
||||
errBuffer := new(strings.Builder)
|
||||
buffer := new(strings.Builder)
|
||||
router := New()
|
||||
DefaultErrorWriter = buffer
|
||||
handleRecovery := func(c *Context, err interface{}) {
|
||||
handleRecovery := func(c *Context, err any) {
|
||||
errBuffer.WriteString(err.(string))
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
}
|
||||
@ -228,7 +225,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "panic recovered")
|
||||
@ -239,7 +236,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
||||
// Debug mode prints the request
|
||||
SetMode(DebugMode)
|
||||
// RUN
|
||||
w = performRequest(router, "GET", "/recovery")
|
||||
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -20,7 +20,7 @@ type Delims struct {
|
||||
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
||||
type HTMLRender interface {
|
||||
// Instance returns an HTML instance.
|
||||
Instance(string, interface{}) Render
|
||||
Instance(string, any) Render
|
||||
}
|
||||
|
||||
// HTMLProduction contains template reference and its delims.
|
||||
@ -41,13 +41,13 @@ type HTMLDebug struct {
|
||||
type HTML struct {
|
||||
Template *template.Template
|
||||
Name string
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||
|
||||
// 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{
|
||||
Template: r.Template,
|
||||
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.
|
||||
func (r HTMLDebug) Instance(name string, data interface{}) Render {
|
||||
func (r HTMLDebug) Instance(name string, data any) Render {
|
||||
return HTML{
|
||||
Template: r.loadTemplate(),
|
||||
Name: name,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -16,46 +16,45 @@ import (
|
||||
|
||||
// JSON contains the given interface object.
|
||||
type JSON struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
// IndentedJSON contains the given interface object.
|
||||
type IndentedJSON struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
// SecureJSON contains the given interface object and its prefix.
|
||||
type SecureJSON struct {
|
||||
Prefix string
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
// JsonpJSON contains the given interface object its callback.
|
||||
type JsonpJSON struct {
|
||||
Callback string
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
// AsciiJSON contains the given interface object.
|
||||
type AsciiJSON struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
// PureJSON contains the given interface object.
|
||||
type PureJSON struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||
var jsonAsciiContentType = []string{"application/json"}
|
||||
var (
|
||||
jsonContentType = []string{"application/json; charset=utf-8"}
|
||||
jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||
jsonASCIIContentType = []string{"application/json"}
|
||||
)
|
||||
|
||||
// Render (JSON) writes data with custom ContentType.
|
||||
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||
if err = WriteJSON(w, r.Data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
func (r JSON) Render(w http.ResponseWriter) error {
|
||||
return WriteJSON(w, r.Data)
|
||||
}
|
||||
|
||||
// WriteContentType (JSON) writes JSON ContentType.
|
||||
@ -64,7 +63,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
jsonBytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
@ -100,8 +99,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
// if the jsonBytes is array values
|
||||
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
||||
bytesconv.StringToBytes("]")) {
|
||||
_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
|
||||
if err != nil {
|
||||
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -128,20 +126,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
}
|
||||
|
||||
callback := template.JSEscapeString(r.Callback)
|
||||
_, err = w.Write(bytesconv.StringToBytes(callback))
|
||||
if err != nil {
|
||||
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bytesconv.StringToBytes("("))
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(ret)
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(ret); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bytesconv.StringToBytes(");"))
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -176,7 +173,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||
|
||||
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonAsciiContentType)
|
||||
writeContentType(w, jsonASCIIContentType)
|
||||
}
|
||||
|
||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||
|
@ -1,9 +1,8 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package render
|
||||
|
||||
@ -13,13 +12,15 @@ import (
|
||||
"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 (
|
||||
_ Render = MsgPack{}
|
||||
)
|
||||
|
||||
// MsgPack contains the given interface object.
|
||||
type MsgPack struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
||||
@ -35,7 +36,7 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
|
||||
}
|
||||
|
||||
// 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)
|
||||
var mh codec.MsgpackHandle
|
||||
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Copyright 2018 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.
|
||||
|
||||
@ -7,12 +7,12 @@ package render
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ProtoBuf contains the given interface object.
|
||||
type ProtoBuf struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var protobufContentType = []string{"application/x-protobuf"}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Copyright 2018 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.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||
// Copyright 2019 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.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -15,21 +15,22 @@ type Render interface {
|
||||
}
|
||||
|
||||
var (
|
||||
_ Render = JSON{}
|
||||
_ Render = IndentedJSON{}
|
||||
_ Render = SecureJSON{}
|
||||
_ Render = JsonpJSON{}
|
||||
_ Render = XML{}
|
||||
_ Render = String{}
|
||||
_ Render = Redirect{}
|
||||
_ Render = Data{}
|
||||
_ Render = HTML{}
|
||||
_ HTMLRender = HTMLDebug{}
|
||||
_ HTMLRender = HTMLProduction{}
|
||||
_ Render = YAML{}
|
||||
_ Render = Reader{}
|
||||
_ Render = AsciiJSON{}
|
||||
_ Render = ProtoBuf{}
|
||||
_ Render = (*JSON)(nil)
|
||||
_ Render = (*IndentedJSON)(nil)
|
||||
_ Render = (*SecureJSON)(nil)
|
||||
_ Render = (*JsonpJSON)(nil)
|
||||
_ Render = (*XML)(nil)
|
||||
_ Render = (*String)(nil)
|
||||
_ Render = (*Redirect)(nil)
|
||||
_ Render = (*Data)(nil)
|
||||
_ Render = (*HTML)(nil)
|
||||
_ HTMLRender = (*HTMLDebug)(nil)
|
||||
_ HTMLRender = (*HTMLProduction)(nil)
|
||||
_ Render = (*YAML)(nil)
|
||||
_ Render = (*Reader)(nil)
|
||||
_ Render = (*AsciiJSON)(nil)
|
||||
_ Render = (*ProtoBuf)(nil)
|
||||
_ Render = (*TOML)(nil)
|
||||
)
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. 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 !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package render
|
||||
|
||||
@ -13,6 +12,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
@ -21,7 +21,7 @@ import (
|
||||
|
||||
func TestRenderMsgPack(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func TestRenderMsgPack(t *testing.T) {
|
||||
|
||||
err := (MsgPack{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
h := new(codec.MsgpackHandle)
|
||||
assert.NotNil(t, h)
|
||||
@ -38,7 +38,7 @@ func TestRenderMsgPack(t *testing.T) {
|
||||
assert.NotNil(t, buf)
|
||||
err = codec.NewEncoder(buf, h).Encode(data)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, w.Body.String(), string(buf.Bytes()))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, w.Body.String(), buf.String())
|
||||
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -8,16 +8,18 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// TODO unit tests
|
||||
@ -25,7 +27,7 @@ import (
|
||||
|
||||
func TestRenderJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
"html": "<b>",
|
||||
}
|
||||
@ -35,29 +37,29 @@ func TestRenderJSON(t *testing.T) {
|
||||
|
||||
err := (JSON{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderJSONPanics(t *testing.T) {
|
||||
func TestRenderJSONError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := make(chan int)
|
||||
|
||||
// json: unsupported type: chan int
|
||||
assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) })
|
||||
require.Error(t, (JSON{data}).Render(w))
|
||||
}
|
||||
|
||||
func TestRenderIndentedJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
}
|
||||
|
||||
err := (IndentedJSON{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -68,12 +70,12 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
|
||||
|
||||
// json: unsupported type: chan int
|
||||
err := (IndentedJSON{data}).Render(w)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderSecureJSON(t *testing.T) {
|
||||
w1 := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
@ -82,19 +84,19 @@ func TestRenderSecureJSON(t *testing.T) {
|
||||
|
||||
err1 := (SecureJSON{"while(1);", data}).Render(w1)
|
||||
|
||||
assert.NoError(t, err1)
|
||||
require.NoError(t, err1)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
datas := []map[string]interface{}{{
|
||||
datas := []map[string]any{{
|
||||
"foo": "bar",
|
||||
}, {
|
||||
"bar": "foo",
|
||||
}}
|
||||
|
||||
err2 := (SecureJSON{"while(1);", datas}).Render(w2)
|
||||
assert.NoError(t, err2)
|
||||
require.NoError(t, err2)
|
||||
assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -105,12 +107,12 @@ func TestRenderSecureJSONFail(t *testing.T) {
|
||||
|
||||
// json: unsupported type: chan int
|
||||
err := (SecureJSON{"while(1);", data}).Render(w)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderJsonpJSON(t *testing.T) {
|
||||
w1 := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
@ -119,33 +121,78 @@ func TestRenderJsonpJSON(t *testing.T) {
|
||||
|
||||
err1 := (JsonpJSON{"x", data}).Render(w1)
|
||||
|
||||
assert.NoError(t, err1)
|
||||
require.NoError(t, err1)
|
||||
assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String())
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
datas := []map[string]interface{}{{
|
||||
datas := []map[string]any{{
|
||||
"foo": "bar",
|
||||
}, {
|
||||
"bar": "foo",
|
||||
}}
|
||||
|
||||
err2 := (JsonpJSON{"x", datas}).Render(w2)
|
||||
assert.NoError(t, err2)
|
||||
require.NoError(t, err2)
|
||||
assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String())
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
type errorWriter struct {
|
||||
bufString string
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = (*errorWriter)(nil)
|
||||
|
||||
func (w *errorWriter) Write(buf []byte) (int, error) {
|
||||
if string(buf) == w.bufString {
|
||||
return 0, errors.New(`write "` + w.bufString + `" error`)
|
||||
}
|
||||
return w.ResponseRecorder.Write(buf)
|
||||
}
|
||||
|
||||
func TestRenderJsonpJSONError(t *testing.T) {
|
||||
ew := &errorWriter{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
}
|
||||
|
||||
jsonpJSON := JsonpJSON{
|
||||
Callback: "foo",
|
||||
Data: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
cb := template.JSEscapeString(jsonpJSON.Callback)
|
||||
ew.bufString = cb
|
||||
err := jsonpJSON.Render(ew) // error was returned while writing callback
|
||||
assert.Equal(t, `write "`+cb+`" error`, err.Error())
|
||||
|
||||
ew.bufString = `(`
|
||||
err = jsonpJSON.Render(ew)
|
||||
assert.Equal(t, `write "`+`(`+`" error`, err.Error())
|
||||
|
||||
data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data
|
||||
ew.bufString = string(data)
|
||||
err = jsonpJSON.Render(ew)
|
||||
assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
|
||||
|
||||
ew.bufString = `);`
|
||||
err = jsonpJSON.Render(ew)
|
||||
assert.Equal(t, `write "`+`);`+`" error`, err.Error())
|
||||
}
|
||||
|
||||
func TestRenderJsonpJSONError2(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
}
|
||||
(JsonpJSON{"", data}).WriteContentType(w)
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
e := (JsonpJSON{"", data}).Render(w)
|
||||
assert.NoError(t, e)
|
||||
require.NoError(t, e)
|
||||
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
@ -157,27 +204,27 @@ func TestRenderJsonpJSONFail(t *testing.T) {
|
||||
|
||||
// json: unsupported type: chan int
|
||||
err := (JsonpJSON{"x", data}).Render(w)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderAsciiJSON(t *testing.T) {
|
||||
w1 := httptest.NewRecorder()
|
||||
data1 := map[string]interface{}{
|
||||
data1 := map[string]any{
|
||||
"lang": "GO语言",
|
||||
"tag": "<br>",
|
||||
}
|
||||
|
||||
err := (AsciiJSON{data1}).Render(w1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
|
||||
assert.Equal(t, "application/json", w1.Header().Get("Content-Type"))
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
data2 := float64(3.1415926)
|
||||
data2 := 3.1415926
|
||||
|
||||
err = (AsciiJSON{data2}).Render(w2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "3.1415926", w2.Body.String())
|
||||
}
|
||||
|
||||
@ -186,22 +233,22 @@ func TestRenderAsciiJSONFail(t *testing.T) {
|
||||
data := make(chan int)
|
||||
|
||||
// json: unsupported type: chan int
|
||||
assert.Error(t, (AsciiJSON{data}).Render(w))
|
||||
require.Error(t, (AsciiJSON{data}).Render(w))
|
||||
}
|
||||
|
||||
func TestRenderPureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
"html": "<b>",
|
||||
}
|
||||
err := (PureJSON{data}).Render(w)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||
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
|
||||
func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
@ -234,25 +281,46 @@ b:
|
||||
d: [3, 4]
|
||||
`
|
||||
(YAML{data}).WriteContentType(w)
|
||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
err := (YAML{data}).Render(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String())
|
||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String())
|
||||
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
type fail struct{}
|
||||
|
||||
// Hook MarshalYAML
|
||||
func (ft *fail) MarshalYAML() (interface{}, error) {
|
||||
func (ft *fail) MarshalYAML() (any, error) {
|
||||
return nil, errors.New("fail")
|
||||
}
|
||||
|
||||
func TestRenderYAMLFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
err := (YAML{&fail{}}).Render(w)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderTOML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
"html": "<b>",
|
||||
}
|
||||
(TOML{data}).WriteContentType(w)
|
||||
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
err := (TOML{data}).Render(w)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "foo = 'bar'\nhtml = '<b>'\n", w.Body.String())
|
||||
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderTOMLFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
err := (TOML{net.IPv4bcast}).Render(w)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// test Protobuf rendering
|
||||
@ -267,12 +335,12 @@ func TestRenderProtoBuf(t *testing.T) {
|
||||
|
||||
(ProtoBuf{data}).WriteContentType(w)
|
||||
protoData, err := proto.Marshal(data)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||
|
||||
err = (ProtoBuf{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(protoData), w.Body.String())
|
||||
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -281,7 +349,7 @@ func TestRenderProtoBufFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := &testdata.Test{}
|
||||
err := (ProtoBuf{data}).Render(w)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderXML(t *testing.T) {
|
||||
@ -295,14 +363,14 @@ func TestRenderXML(t *testing.T) {
|
||||
|
||||
err := (XML{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
|
||||
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderRedirect(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/test-redirect", nil)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
data1 := Redirect{
|
||||
Code: http.StatusMovedPermanently,
|
||||
@ -312,7 +380,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
err = data1.Render(w)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
data2 := Redirect{
|
||||
Code: http.StatusOK,
|
||||
@ -323,7 +391,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
w = httptest.NewRecorder()
|
||||
assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() {
|
||||
err := data2.Render(w)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
data3 := Redirect{
|
||||
@ -334,7 +402,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
err = data3.Render(w)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// only improve coverage
|
||||
data2.WriteContentType(w)
|
||||
@ -349,7 +417,7 @@ func TestRenderData(t *testing.T) {
|
||||
Data: data,
|
||||
}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "#!PNG some raw data", w.Body.String())
|
||||
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -359,16 +427,16 @@ func TestRenderString(t *testing.T) {
|
||||
|
||||
(String{
|
||||
Format: "hello %s %d",
|
||||
Data: []interface{}{},
|
||||
Data: []any{},
|
||||
}).WriteContentType(w)
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
err := (String{
|
||||
Format: "hola %s %d",
|
||||
Data: []interface{}{"manu", 2},
|
||||
Data: []any{"manu", 2},
|
||||
}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hola manu 2", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -378,10 +446,10 @@ func TestRenderStringLenZero(t *testing.T) {
|
||||
|
||||
err := (String{
|
||||
Format: "hola %s %d",
|
||||
Data: []interface{}{},
|
||||
Data: []any{},
|
||||
}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hola %s %d", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -391,13 +459,13 @@ func TestRenderHTMLTemplate(t *testing.T) {
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
|
||||
htmlRender := HTMLProduction{Template: templ}
|
||||
instance := htmlRender.Instance("t", map[string]interface{}{
|
||||
instance := htmlRender.Instance("t", map[string]any{
|
||||
"name": "alexandernyquist",
|
||||
})
|
||||
|
||||
err := instance.Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
@ -407,55 +475,58 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
||||
templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
|
||||
|
||||
htmlRender := HTMLProduction{Template: templ}
|
||||
instance := htmlRender.Instance("", map[string]interface{}{
|
||||
instance := htmlRender.Instance("", map[string]any{
|
||||
"name": "alexandernyquist",
|
||||
})
|
||||
|
||||
err := instance.Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
|
||||
htmlRender := HTMLDebug{
|
||||
Files: []string{"../testdata/template/hello.tmpl"},
|
||||
Glob: "",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
}
|
||||
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
||||
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||
"name": "thinkerou",
|
||||
})
|
||||
|
||||
err := instance.Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: nil,
|
||||
htmlRender := HTMLDebug{
|
||||
Files: nil,
|
||||
Glob: "../testdata/template/hello*",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
}
|
||||
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
||||
instance := htmlRender.Instance("hello.tmpl", map[string]any{
|
||||
"name": "thinkerou",
|
||||
})
|
||||
|
||||
err := instance.Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderHTMLDebugPanics(t *testing.T) {
|
||||
htmlRender := HTMLDebug{Files: nil,
|
||||
htmlRender := HTMLDebug{
|
||||
Files: nil,
|
||||
Glob: "",
|
||||
Delims: Delims{"{{", "}}"},
|
||||
FuncMap: nil,
|
||||
@ -478,7 +549,7 @@ func TestRenderReader(t *testing.T) {
|
||||
Headers: headers,
|
||||
}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, body, w.Body.String())
|
||||
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length"))
|
||||
@ -501,10 +572,23 @@ func TestRenderReaderNoContentLength(t *testing.T) {
|
||||
Headers: headers,
|
||||
}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, body, w.Body.String())
|
||||
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
|
||||
assert.NotContains(t, "Content-Length", w.Header())
|
||||
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
|
||||
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
|
||||
}
|
||||
|
||||
func TestRenderWriteError(t *testing.T) {
|
||||
data := []interface{}{"value1", "value2"}
|
||||
prefix := "my-prefix:"
|
||||
r := SecureJSON{Data: data, Prefix: prefix}
|
||||
ew := &errorWriter{
|
||||
bufString: prefix,
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
}
|
||||
err := r.Render(ew)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `write "my-prefix:" error`, err.Error())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
// String contains the given interface object slice and its format.
|
||||
type String struct {
|
||||
Format string
|
||||
Data []interface{}
|
||||
Data []any
|
||||
}
|
||||
|
||||
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.
|
||||
func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) {
|
||||
func WriteString(w http.ResponseWriter, format string, data []any) (err error) {
|
||||
writeContentType(w, plainContentType)
|
||||
if len(data) > 0 {
|
||||
_, err = fmt.Fprintf(w, format, data...)
|
||||
|
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)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
// XML contains the given interface object.
|
||||
type XML struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -7,15 +7,15 @@ package render
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// YAML contains the given interface object.
|
||||
type YAML struct {
|
||||
Data interface{}
|
||||
Data any
|
||||
}
|
||||
|
||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||
var yamlContentType = []string{"application/yaml; charset=utf-8"}
|
||||
|
||||
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
||||
func (r YAML) Render(w http.ResponseWriter) error {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -23,23 +23,23 @@ type ResponseWriter interface {
|
||||
http.Flusher
|
||||
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
|
||||
|
||||
// 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()
|
||||
Size() int
|
||||
|
||||
// Writes the string into the response body.
|
||||
// WriteString writes the string into the response body.
|
||||
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
|
||||
|
||||
// Forces to write the http header (status code + headers).
|
||||
// WriteHeaderNow forces to write the http header (status code + headers).
|
||||
WriteHeaderNow()
|
||||
|
||||
// get the http.Pusher for server push
|
||||
// Pusher get the http.Pusher for server push
|
||||
Pusher() http.Pusher
|
||||
}
|
||||
|
||||
@ -49,7 +49,11 @@ type responseWriter struct {
|
||||
status int
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
var _ ResponseWriter = (*responseWriter)(nil)
|
||||
|
||||
func (w *responseWriter) Unwrap() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||
w.ResponseWriter = writer
|
||||
@ -61,6 +65,7 @@ func (w *responseWriter) WriteHeader(code int) {
|
||||
if code > 0 && w.status != code {
|
||||
if w.Written() {
|
||||
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||
return
|
||||
}
|
||||
w.status = code
|
||||
}
|
||||
@ -107,12 +112,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotify interface.
|
||||
// CloseNotify implements the http.CloseNotifier interface.
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush implements the http.Flush interface.
|
||||
// Flush implements the http.Flusher interface.
|
||||
func (w *responseWriter) Flush() {
|
||||
w.WriteHeaderNow()
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TODO
|
||||
@ -17,17 +18,25 @@ import (
|
||||
// func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
// func (w *responseWriter) Flush() {
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
var _ http.ResponseWriter = &responseWriter{}
|
||||
var _ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
||||
var _ http.Hijacker = ResponseWriter(&responseWriter{})
|
||||
var _ http.Flusher = ResponseWriter(&responseWriter{})
|
||||
var _ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
||||
var (
|
||||
_ ResponseWriter = &responseWriter{}
|
||||
_ http.ResponseWriter = &responseWriter{}
|
||||
_ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
||||
_ http.Hijacker = ResponseWriter(&responseWriter{})
|
||||
_ http.Flusher = ResponseWriter(&responseWriter{})
|
||||
_ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetMode(TestMode)
|
||||
}
|
||||
|
||||
func TestResponseWriterUnwrap(t *testing.T) {
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{ResponseWriter: testWriter}
|
||||
assert.Same(t, testWriter, writer.Unwrap())
|
||||
}
|
||||
|
||||
func TestResponseWriterReset(t *testing.T) {
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
@ -87,13 +96,13 @@ func TestResponseWriterWrite(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.Equal(t, http.StatusOK, testWriter.Code)
|
||||
assert.Equal(t, "hola", testWriter.Body.String())
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err = w.Write([]byte(" adios"))
|
||||
assert.Equal(t, 6, n)
|
||||
assert.Equal(t, 10, w.Size())
|
||||
assert.Equal(t, "hola adios", testWriter.Body.String())
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestResponseWriterHijack(t *testing.T) {
|
||||
@ -104,7 +113,7 @@ func TestResponseWriterHijack(t *testing.T) {
|
||||
|
||||
assert.Panics(t, func() {
|
||||
_, _, err := w.Hijack()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
assert.True(t, w.Written())
|
||||
|
||||
@ -127,6 +136,54 @@ func TestResponseWriterFlush(t *testing.T) {
|
||||
|
||||
// should return 500
|
||||
resp, err := http.Get(testServer.URL)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestResponseWriterStatusCode(t *testing.T) {
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
writer.reset(testWriter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.True(t, w.Written())
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
|
||||
// status must be 200 although we tried to change it
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
}
|
||||
|
||||
// mockPusherResponseWriter is an http.ResponseWriter that implements http.Pusher.
|
||||
type mockPusherResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (m *mockPusherResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nonPusherResponseWriter is an http.ResponseWriter that does not implement http.Pusher.
|
||||
type nonPusherResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func TestPusherWithPusher(t *testing.T) {
|
||||
rw := &mockPusherResponseWriter{}
|
||||
w := &responseWriter{ResponseWriter: rw}
|
||||
|
||||
pusher := w.Pusher()
|
||||
assert.NotNil(t, pusher, "Expected pusher to be non-nil")
|
||||
}
|
||||
|
||||
func TestPusherWithoutPusher(t *testing.T) {
|
||||
rw := &nonPusherResponseWriter{}
|
||||
w := &responseWriter{ResponseWriter: rw}
|
||||
|
||||
pusher := w.Pusher()
|
||||
assert.Nil(t, pusher, "Expected pusher to be nil")
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -11,6 +11,18 @@ import (
|
||||
"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.
|
||||
type IRouter interface {
|
||||
IRoutes
|
||||
@ -30,8 +42,10 @@ type IRoutes interface {
|
||||
PUT(string, ...HandlerFunc) IRoutes
|
||||
OPTIONS(string, ...HandlerFunc) IRoutes
|
||||
HEAD(string, ...HandlerFunc) IRoutes
|
||||
Match([]string, string, ...HandlerFunc) IRoutes
|
||||
|
||||
StaticFile(string, string) IRoutes
|
||||
StaticFileFS(string, string, http.FileSystem) IRoutes
|
||||
Static(string, string) IRoutes
|
||||
StaticFS(string, http.FileSystem) IRoutes
|
||||
}
|
||||
@ -45,7 +59,7 @@ type RouterGroup struct {
|
||||
root bool
|
||||
}
|
||||
|
||||
var _ IRouter = &RouterGroup{}
|
||||
var _ IRouter = (*RouterGroup)(nil)
|
||||
|
||||
// Use adds middleware to the group, see example code in GitHub.
|
||||
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
|
||||
@ -87,43 +101,43 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
|
||||
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||
// communication with a proxy).
|
||||
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")
|
||||
}
|
||||
return group.handle(httpMethod, relativePath, handlers)
|
||||
}
|
||||
|
||||
// POST is a shortcut for router.Handle("POST", path, handle).
|
||||
// POST is a shortcut for router.Handle("POST", path, handlers).
|
||||
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodPost, relativePath, handlers)
|
||||
}
|
||||
|
||||
// GET is a shortcut for router.Handle("GET", path, handle).
|
||||
// GET is a shortcut for router.Handle("GET", path, handlers).
|
||||
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodGet, relativePath, handlers)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle).
|
||||
// DELETE is a shortcut for router.Handle("DELETE", path, handlers).
|
||||
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodDelete, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
|
||||
// PATCH is a shortcut for router.Handle("PATCH", path, handlers).
|
||||
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodPatch, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for router.Handle("PUT", path, handle).
|
||||
// PUT is a shortcut for router.Handle("PUT", path, handlers).
|
||||
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodPut, relativePath, handlers)
|
||||
}
|
||||
|
||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
|
||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers).
|
||||
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodOptions, relativePath, handlers)
|
||||
}
|
||||
|
||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
|
||||
// HEAD is a shortcut for router.Handle("HEAD", path, handlers).
|
||||
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle(http.MethodHead, relativePath, handlers)
|
||||
}
|
||||
@ -131,27 +145,43 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo
|
||||
// Any registers a route that matches all the HTTP methods.
|
||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
group.handle(http.MethodGet, relativePath, handlers)
|
||||
group.handle(http.MethodPost, 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)
|
||||
for _, method := range anyMethods {
|
||||
group.handle(method, relativePath, handlers)
|
||||
}
|
||||
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
// Match registers a route that matches the specified methods that you declared.
|
||||
func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
for _, method := range methods {
|
||||
group.handle(method, relativePath, handlers)
|
||||
}
|
||||
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
// StaticFile registers a single route in order to serve a single file of the local filesystem.
|
||||
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
||||
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 uses: 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, "*") {
|
||||
panic("URL parameters can not be used when serving a static file")
|
||||
}
|
||||
handler := func(c *Context) {
|
||||
c.File(filepath)
|
||||
}
|
||||
group.GET(relativePath, handler)
|
||||
group.HEAD(relativePath, handler)
|
||||
return group.returnObj()
|
||||
@ -162,13 +192,14 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
||||
// of the Router's NotFound handler.
|
||||
// To use the operating system's file system implementation,
|
||||
// use :
|
||||
// router.Static("/static", "/var/www")
|
||||
//
|
||||
// router.Static("/static", "/var/www")
|
||||
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
|
||||
return group.StaticFS(relativePath, Dir(root, false))
|
||||
}
|
||||
|
||||
// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
|
||||
// Gin by default user: gin.Dir()
|
||||
// Gin by default uses: gin.Dir()
|
||||
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
|
||||
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||
panic("URL parameters can not be used when serving a static folder")
|
||||
@ -187,7 +218,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
||||
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||
|
||||
return func(c *Context) {
|
||||
if _, noListing := fs.(*onlyFilesFS); noListing {
|
||||
if _, noListing := fs.(*OnlyFilesFS); noListing {
|
||||
c.Writer.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
@ -209,9 +240,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
||||
|
||||
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||
finalSize := len(group.Handlers) + len(handlers)
|
||||
if finalSize >= int(abortIndex) {
|
||||
panic("too many handlers")
|
||||
}
|
||||
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||
mergedHandlers := make(HandlersChain, finalSize)
|
||||
copy(mergedHandlers, group.Handlers)
|
||||
copy(mergedHandlers[len(group.Handlers):], handlers)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) {
|
||||
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, "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, "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()
|
||||
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...)
|
||||
|
||||
handlers2 := make([]HandlerFunc, 26)
|
||||
assert.Panics(t, func() {
|
||||
handlers2 := make([]HandlerFunc, maximumCnt+1)
|
||||
assert.PanicsWithValue(t, panicValue, func() {
|
||||
router.Use(handlers2...)
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
assert.PanicsWithValue(t, panicValue, func() {
|
||||
router.GET("/", handlers2...)
|
||||
})
|
||||
}
|
||||
@ -171,8 +186,10 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
|
||||
assert.Equal(t, r, r.PUT("/", handler))
|
||||
assert.Equal(t, r, r.OPTIONS("/", handler))
|
||||
assert.Equal(t, r, r.HEAD("/", handler))
|
||||
assert.Equal(t, r, r.Match([]string{http.MethodPut, http.MethodPatch}, "/match", handler))
|
||||
|
||||
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.StaticFS("/static2", Dir(".", false)))
|
||||
}
|
||||
|
302
routes_test.go
302
routes_test.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,7 +6,6 @@ package gin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@ -14,6 +13,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
@ -21,7 +21,8 @@ type header struct {
|
||||
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)
|
||||
for _, h := range headers {
|
||||
req.Header.Add(h.Key, h.Value)
|
||||
@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) {
|
||||
passed = true
|
||||
})
|
||||
|
||||
w := performRequest(r, method, "/test")
|
||||
w := PerformRequest(r, method, "/test")
|
||||
assert.True(t, passed)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
performRequest(r, method, "/test2")
|
||||
PerformRequest(r, method, "/test2")
|
||||
assert.True(t, passedAny)
|
||||
}
|
||||
|
||||
@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) {
|
||||
passed = true
|
||||
})
|
||||
|
||||
w := performRequest(router, method, "/test")
|
||||
w := PerformRequest(router, method, "/test")
|
||||
|
||||
assert.False(t, passed)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) {
|
||||
passed = true
|
||||
})
|
||||
|
||||
w := performRequest(router, method, "/test")
|
||||
w := PerformRequest(router, method, "/test")
|
||||
|
||||
assert.False(t, passed)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) {
|
||||
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, "called", w.Body.String())
|
||||
@ -150,50 +151,98 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||
router.POST("/path3", 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, 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, 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, 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, http.StatusTemporaryRedirect, w.Code)
|
||||
|
||||
w = performRequest(router, http.MethodGet, "/path")
|
||||
w = PerformRequest(router, http.MethodGet, "/path")
|
||||
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)
|
||||
|
||||
w = performRequest(router, http.MethodPost, "/path3")
|
||||
w = PerformRequest(router, http.MethodPost, "/path3")
|
||||
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)
|
||||
|
||||
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, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
||||
assert.Equal(t, 200, w.Code)
|
||||
w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"})
|
||||
assert.Equal(t, "/api/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"})
|
||||
assert.Equal(t, "/api/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
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, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
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, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"})
|
||||
assert.Equal(t, "//path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"})
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"})
|
||||
assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"})
|
||||
assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"})
|
||||
assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"})
|
||||
assert.Equal(t, "api/api/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"})
|
||||
assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"})
|
||||
assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
router.RedirectTrailingSlash = false
|
||||
|
||||
w = performRequest(router, http.MethodGet, "/path/")
|
||||
w = PerformRequest(router, http.MethodGet, "/path/")
|
||||
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)
|
||||
w = performRequest(router, http.MethodPost, "/path3/")
|
||||
w = PerformRequest(router, http.MethodPost, "/path3/")
|
||||
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)
|
||||
}
|
||||
|
||||
@ -207,19 +256,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
router.POST("/PATH3", 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, 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, 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, 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, http.StatusTemporaryRedirect, w.Code)
|
||||
}
|
||||
@ -248,7 +297,7 @@ func TestRouteParamsByName(t *testing.T) {
|
||||
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, "john", name)
|
||||
@ -281,7 +330,46 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||
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, "john", name)
|
||||
assert.Equal(t, "smith", lastName)
|
||||
assert.Equal(t, "/is/super/great", wild)
|
||||
}
|
||||
|
||||
// TestRouteParamsNotEmpty tests that context parameters will be set
|
||||
// even if a route with params/wildcards is registered after the context
|
||||
// initialisation (which happened in a previous requests).
|
||||
func TestRouteParamsNotEmpty(t *testing.T) {
|
||||
name := ""
|
||||
lastName := ""
|
||||
wild := ""
|
||||
router := New()
|
||||
|
||||
w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
|
||||
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
|
||||
name = c.Params.ByName("name")
|
||||
lastName = c.Params.ByName("last_name")
|
||||
var ok bool
|
||||
wild, ok = c.Params.Get("wild")
|
||||
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, name, c.Param("name"))
|
||||
assert.Equal(t, lastName, c.Param("last_name"))
|
||||
|
||||
assert.Empty(t, c.Param("wtf"))
|
||||
assert.Empty(t, c.Params.ByName("wtf"))
|
||||
|
||||
wtf, ok := c.Params.Get("wtf")
|
||||
assert.Empty(t, wtf)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
w = PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "john", name)
|
||||
@ -293,13 +381,13 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||
func TestRouteStaticFile(t *testing.T) {
|
||||
// SETUP file
|
||||
testRoot, _ := os.Getwd()
|
||||
f, err := ioutil.TempFile(testRoot, "")
|
||||
f, err := os.CreateTemp(testRoot, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
_, err = f.WriteString("Gin Web Framework")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
dir, filename := filepath.Split(f.Name())
|
||||
@ -309,16 +397,50 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
router.Static("/using_static", dir)
|
||||
router.StaticFile("/result", f.Name())
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/using_static/"+filename)
|
||||
w2 := performRequest(router, http.MethodGet, "/result")
|
||||
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
|
||||
w2 := PerformRequest(router, http.MethodGet, "/result")
|
||||
|
||||
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")
|
||||
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
|
||||
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 := os.CreateTemp(testRoot, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
_, err = f.WriteString("Gin Web Framework")
|
||||
require.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, http.StatusOK, w3.Code)
|
||||
@ -329,7 +451,7 @@ func TestRouteStaticListingDir(t *testing.T) {
|
||||
router := New()
|
||||
router.StaticFS("/", Dir("./", true))
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "gin.go")
|
||||
@ -341,7 +463,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
||||
router := New()
|
||||
router.Static("/", "./")
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/")
|
||||
w := PerformRequest(router, http.MethodGet, "/")
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||
@ -356,12 +478,14 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
||||
})
|
||||
static.Static("/", "./")
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/gin.go")
|
||||
w := PerformRequest(router, http.MethodGet, "/gin.go")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "package gin")
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||
// 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, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified"))
|
||||
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"))
|
||||
}
|
||||
@ -370,13 +494,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
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)
|
||||
|
||||
router.NoMethod(func(c *Context) {
|
||||
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, http.StatusTeapot, w.Code)
|
||||
}
|
||||
@ -387,21 +511,33 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
|
||||
// add one methodTree to trees
|
||||
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *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)
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedEnabled3(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
router.GET("/path", func(c *Context) {})
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := PerformRequest(router, http.MethodPut, "/path")
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
allowed := w.Header().Get("Allow")
|
||||
assert.Contains(t, allowed, http.MethodGet)
|
||||
assert.Contains(t, allowed, http.MethodPost)
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = false
|
||||
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)
|
||||
|
||||
router.NoMethod(func(c *Context) {
|
||||
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, http.StatusNotFound, w.Code)
|
||||
}
|
||||
@ -421,10 +557,10 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
w := performRequest(router, "GET", tr.route)
|
||||
w := PerformRequest(router, http.MethodGet, tr.route)
|
||||
assert.Equal(t, tr.code, w.Code)
|
||||
if w.Code != http.StatusNotFound {
|
||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||
assert.Equal(t, tr.location, w.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -451,10 +587,10 @@ func TestRouterNotFound(t *testing.T) {
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
}
|
||||
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)
|
||||
if w.Code != http.StatusNotFound {
|
||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||
assert.Equal(t, tr.location, w.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,34 +600,49 @@ func TestRouterNotFound(t *testing.T) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
notFound = true
|
||||
})
|
||||
w := performRequest(router, http.MethodGet, "/nope")
|
||||
w := PerformRequest(router, http.MethodGet, "/nope")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.True(t, notFound)
|
||||
|
||||
// Test other method than GET (want 307 instead of 301)
|
||||
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, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
||||
|
||||
// Test special case where no node for the prefix "/" exists
|
||||
router = New()
|
||||
router.GET("/a", func(c *Context) {})
|
||||
w = performRequest(router, http.MethodGet, "/")
|
||||
w = PerformRequest(router, http.MethodGet, "/")
|
||||
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(http.StatusOK, "login")
|
||||
}
|
||||
})
|
||||
router.GET("/logout", func(c *Context) {
|
||||
c.String(http.StatusOK, "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) {
|
||||
router := New()
|
||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||
router.NoRoute(func(c *Context) {
|
||||
c.String(404, "non existent")
|
||||
c.String(http.StatusNotFound, "non existent")
|
||||
})
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/nonexistent")
|
||||
w := PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||
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())
|
||||
}
|
||||
|
||||
@ -501,7 +652,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
|
||||
router.StaticFS("/", http.FileSystem(http.Dir(".")))
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
performRequest(router, http.MethodGet, "/nonexistent")
|
||||
PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||
})
|
||||
}
|
||||
|
||||
@ -518,11 +669,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
|
||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||
|
||||
// First access
|
||||
performRequest(router, http.MethodGet, "/nonexistent")
|
||||
PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||
assert.Equal(t, 1, middlewareCalledNum)
|
||||
|
||||
// Second access
|
||||
performRequest(router, http.MethodHead, "/nonexistent")
|
||||
PerformRequest(router, http.MethodHead, "/nonexistent")
|
||||
assert.Equal(t, 2, middlewareCalledNum)
|
||||
}
|
||||
|
||||
@ -541,7 +692,7 @@ func TestRouteRawPath(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -561,19 +712,19 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||
route := New()
|
||||
route.Use(func(c *Context) {
|
||||
c.Status(421)
|
||||
c.Status(http.StatusMisdirectedRequest)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
w := performRequest(route, http.MethodGet, "/NotFound")
|
||||
assert.Equal(t, 421, w.Code)
|
||||
w := PerformRequest(route, http.MethodGet, "/NotFound")
|
||||
assert.Equal(t, http.StatusMisdirectedRequest, w.Code)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
@ -606,7 +757,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
w := performRequest(router, http.MethodGet, route)
|
||||
w := PerformRequest(router, http.MethodGet, route)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
@ -616,6 +767,25 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
|
||||
r := New()
|
||||
r.HandleMethodNotAllowed = true
|
||||
|
||||
base := r.Group("base")
|
||||
base.GET("/metrics", handlerTest1)
|
||||
|
||||
v1 := base.Group("v1")
|
||||
|
||||
v1.GET("/:id/devices", handlerTest1)
|
||||
v1.GET("/user/:id/groups", handlerTest1)
|
||||
|
||||
v1.GET("/orgs/:id", handlerTest1)
|
||||
v1.DELETE("/orgs/:id", handlerTest1)
|
||||
|
||||
w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -9,7 +9,15 @@ import "net/http"
|
||||
// CreateTestContext returns a fresh engine and context for testing purposes
|
||||
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||
r = New()
|
||||
c = r.allocateContext()
|
||||
c = r.allocateContext(0)
|
||||
c.reset()
|
||||
c.writermem.reset(w)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTestContextOnly returns a fresh context base on the engine for testing purposes
|
||||
func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
|
||||
c = r.allocateContext(r.maxParams)
|
||||
c.reset()
|
||||
c.writermem.reset(w)
|
||||
return
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user