Merge branch 'master' into afiune/add-StringHTML

This commit is contained in:
Salim Afiune 2022-07-05 14:47:01 -07:00 committed by GitHub
commit 2f4c44627b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 4341 additions and 1388 deletions

View File

@ -1,7 +1,7 @@
- With pull requests: - With pull requests:
- Open your pull request against `master` - Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them. - Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integration systems such as TravisCI. - It should pass all tests in the available continuous integration systems such as GitHub Actions.
- You should add/modify tests to cover your proposed code changes. - You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README. - If your pull request contains a new feature, please document it on the README.

10
.github/dependabot.yml vendored Normal file
View 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
View File

@ -0,0 +1,49 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '0 17 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
# TODO: Enable for javascript later
language: [ 'go']
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

84
.github/workflows/gin.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Run Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: '^1.16'
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v3.2.0
with:
version: v1.45.0
args: --verbose
test:
needs: lint
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
go: [1.15, 1.16, 1.17, 1.18]
test-tags: ['', nomsgpack]
include:
- os: ubuntu-latest
go-build: ~/.cache/go-build
- os: macos-latest
go-build: ~/Library/Caches/go-build
name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
runs-on: ${{ matrix.os }}
env:
GO111MODULE: on
TESTTAGS: ${{ matrix.test-tags }}
GOPROXY: https://proxy.golang.org
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Checkout Code
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- uses: actions/cache@v3
with:
path: |
${{ matrix.go-build }}
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run Tests
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
notification-gitter:
needs: test
runs-on: ubuntu-latest
steps:
- name: Notification failure message
if: failure()
run: |
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4
- name: Notification success message
if: success()
run: |
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4

34
.github/workflows/goreleaser.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Goreleaser
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

39
.golangci.yml Normal file
View File

@ -0,0 +1,39 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- depguard
- dogsled
- durationcheck
- errcheck
- errorlint
- exportloopref
- gci
- gofmt
- goimports
- gosec
- misspell
- nakedret
- nilerr
- nolintlint
- revive
- wastedassign
issues:
exclude-rules:
- linters:
- structcheck
- unused
text: "`data` is unused"
- linters:
- staticcheck
text: "SA1019:"
- linters:
- revive
text: "var-naming:"
- linters:
- revive
text: "exported:"
- path: _test\.go
linters:
- gosec # security is not make sense in tests

57
.goreleaser.yaml Normal file
View File

@ -0,0 +1,57 @@
project_name: gin
builds:
-
# If true, skip the build.
# Useful for library projects.
# Default is false
skip: true
changelog:
# Set it to true if you wish to skip the changelog generation.
# This may result in an empty release notes on GitHub/GitLab/Gitea.
skip: false
# Changelog generation implementation to use.
#
# Valid options are:
# - `git`: uses `git log`;
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
#
# Defaults to `git`.
use: git
# Sorts the changelog by the commit's messages.
# Could either be asc, desc or empty
# Default is empty
sort: asc
# Group commits messages by given regex and title.
# Order value defines the order of the groups.
# Proving no regex means all commits will be grouped under the default group.
# Groups are disabled when using github-native, as it already groups things by itself.
#
# Default is no groups.
groups:
- title: Features
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- title: 'Bug fixes'
regexp: "^.*fix[(\\w)]*:+.*$"
order: 1
- title: 'Enhancements'
regexp: "^.*chore[(\\w)]*:+.*$"
order: 2
- title: Others
order: 999
filters:
# Commit messages matching the regexp listed here will be removed from
# the changelog
# Default is empty
exclude:
- '^docs'
- 'CICD'
- typo

View File

@ -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

View File

@ -2,230 +2,405 @@ List of all the awesome people working to make Gin the best Web Framework in Go.
## gin 1.x series authors ## gin 1.x series authors
**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) **Gin Core Team:** Bo-Yi Wu (@appleboy), thinkerou (@thinkerou), Javier Provecho (@javierprovecho)
## gin 0.x series authors ## gin 0.x series authors
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) **Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
------
People and companies, who have contributed, in alphabetical order. People and companies, who have contributed, in alphabetical order.
**@858806258 (杰哥)** - 178inaba <178inaba@users.noreply.github.com>
- Fix typo in example - A. F <hello@clivern.com>
- ABHISHEK SONI <abhishek.rocks26@gmail.com>
- Abhishek Chanda <achanda@users.noreply.github.com>
**@achedeuzot (Klemen Sever)** - Abner Chen <houjunchen@gmail.com>
- Fix newline debug printing - AcoNCodes <acongame@gmail.com>
- Adam Dratwinski <adam.dratwinski@gmail.com>
- Adam Mckaig <adam.mckaig@gmail.com>
**@adammck (Adam Mckaig)** - Adam Zielinski <MusicAdam@users.noreply.github.com>
- Add MIT license - Adonis <donileo@gmail.com>
- Alan Wang <azzwacb9001@126.com>
- Albin Gilles <gilles.albin@gmail.com>
**@AlexanderChen1989 (Alexander)** - Aleksandr Didenko <aa.didenko@yandex.ru>
- Typos in README - Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
- Alex <AWulkan@users.noreply.github.com>
- Alexander <alexanderchenmh@gmail.com>
**@alexanderdidenko (Aleksandr Didenko)** - Alexander Lokhman <alex.lokhman@gmail.com>
- Add support multipart/form-data - Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com>
- Alexander Nyquist <nyquist.alexander@gmail.com>
- Allen Ren <kulong0105@gmail.com>
**@alexandernyquist (Alexander Nyquist)** - AllinGo <tanhp@outlook.com>
- Using template.Must to fix multiple return issue - Ammar Bandukwala <ammar@ammar.io>
- ★ Added support for OPTIONS verb - An Xiao (Luffy) <hac@zju.edu.cn>
- ★ Setting response headers before calling WriteHeader - Andre Dublin <81dublin@gmail.com>
- Improved documentation for model binding - Andrew Szeto <github@jabagawee.com>
- ★ Added Content.Redirect() - Andrey Abramov <andreyabramov.aaa@gmail.com>
- ★ Added tons of Unit tests - Andrey Nering <andrey.nering@gmail.com>
- Andrey Smirnov <Smirnov.Andrey@gmail.com>
- Andrii Bubis <firstrow@gmail.com>
**@austinheap (Austin Heap)** - André Bazaglia <bazaglia@users.noreply.github.com>
- Added travis CI integration - Andy Pan <panjf2000@gmail.com>
- Antoine GIRARD <sapk@users.noreply.github.com>
- Anup Kumar Panwar <1anuppanwar@gmail.com>
**@andredublin (Andre Dublin)** - Aravinth Sundaram <gosh.aravind@gmail.com>
- Fix typo in comment - Artem <horechek@gmail.com>
- Ashwani <ashwanisharma686@gmail.com>
- Aurelien Regat-Barrel <arb@cyberkarma.net>
**@bredov (Ludwig Valda Vasquez)** - Austin Heap <me@austinheap.com>
- Fix html templating in debug mode - Barnabus <jbampton@users.noreply.github.com>
- Bo-Yi Wu <appleboy.tw@gmail.com>
- Boris Borshevsky <BorisBorshevsky@gmail.com>
**@bluele (Jun Kimura)** - Boyi Wu <p581581@gmail.com>
- Fixes code examples in README - BradyBromley <51128276+BradyBromley@users.noreply.github.com>
- Brendan Fosberry <brendan@shopkeep.com>
- Brian Wigginton <brianwigginton@gmail.com>
**@chad-russell** - Carlos Eduardo <carlosedp@gmail.com>
- ★ Support for serializing gin.H into XML - Chad Russell <chaddouglasrussell@gmail.com>
- Charles <cxjava@gmail.com>
- Christian Muehlhaeuser <muesli@gmail.com>
**@dickeyxxx (Jeff Dickey)** - Christian Persson <saser@live.se>
- Typos in README - Christopher Harrington <ironiridis@gmail.com>
- Add example about serving static files - Damon Zhao <yijun.zhao@outlook.com>
- Dan Markham <dmarkham@gmail.com>
- Dang Nguyen <hoangdang.me@gmail.com>
**@donileo (Adonis)** - Daniel Krom <kromdan@gmail.com>
- Add NoMethod handler - Daniel M. Lambea <dmlambea@gmail.com>
- Danieliu <liudanking@gmail.com>
- David Irvine <aviddiviner@gmail.com>
**@dutchcoders (DutchCoders)** - David Zhang <crispgm@gmail.com>
- ★ Fix security bug that allows client to spoof ip - Davor Kapsa <dvrkps@users.noreply.github.com>
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate - DeathKing <DeathKing@users.noreply.github.com>
- Dennis Cho <47404603+forest747@users.noreply.github.com>
- Dmitry Dorogin <dmirogin@ya.ru>
**@el3ctro- (Joshua Loper)** - Dmitry Kutakov <vkd.castle@gmail.com>
- Fix typo in example - Dmitry Sedykh <dmitrys@d3h.local>
- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com>
- Donn Pebe <iam@donnpebe.com>
**@ethankan (Ethan Kan)** - Dustin Decker <dustindecker@protonmail.com>
- Unsigned integers in binding - Eason Lin <easonlin404@gmail.com>
- Edward Betts <edward@4angle.com>
- Egor Seredin <4819888+agmt@users.noreply.github.com>
**(Evgeny Persienko)** - Emmanuel Goh <emmanuel@visenze.com>
- Validate sub structures - Equim <sayaka@ekyu.moe>
- Eren A. Akyol <eren@redmc.me>
- Eric_Lee <xplzv@126.com>
**@frankbille (Frank Bille)** - Erik Bender <erik.bender@develerik.dev>
- Add support for HTTP Realm Auth - Ethan Kan <ethankan@neoplot.com>
- Evgeny Persienko <e.persienko@office.ngs.ru>
- Faisal Alam <ifaisalalam@gmail.com>
**@fmd (Fareed Dudhia)** - Fareed Dudhia <fareeddudhia@googlemail.com>
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate - Filip Figiel <figiel.filip@gmail.com>
- Florian Polster <couchpolster@icqmail.com>
- Frank Bille <github@frankbille.dk>
**@ironiridis (Christopher Harrington)** - Franz Bettag <franz@bett.ag>
- Remove old reference - Ganlv <ganlvtech@users.noreply.github.com>
- Gaozhen Ying <yinggaozhen@hotmail.com>
- George Gabolaev <gabolaev98@gmail.com>
**@jammie-stackhouse (Jamie Stackhouse)** - George Kirilenko <necryin@users.noreply.github.com>
- Add more shortcuts for router methods - Georges Varouchas <georges.varouchas@gmail.com>
- Gordon Tyler <gordon@doxxx.net>
- Harindu Perera <harinduenator@gmail.com>
**@jasonrhansen** - Helios <674876158@qq.com>
- Fix spelling and grammar errors in documentation - Henry Kwan <piengeng@users.noreply.github.com>
- Henry Yee <henry@yearning.io>
- Himanshu Mishra <OrkoHunter@users.noreply.github.com>
**@JasonSoft (Jason Lee)** - Hiroyuki Tanaka <h.tanaka.0325@gmail.com>
- Fix typo in comment - Ibraheem Ahmed <ibrah1440@gmail.com>
- Ignacio Galindo <joiggama@gmail.com>
- Igor H. Vieira <zignd.igor@gmail.com>
**@joiggama (Ignacio Galindo)** - Ildar1111 <54001462+Ildar1111@users.noreply.github.com>
- Add utf-8 charset header on renders - Iskander (Alex) Sharipov <iskander.sharipov@intel.com>
- Ismail Gjevori <isgjevori@protonmail.com>
- Ivan Chen <allenivan@gmail.com>
**@julienschmidt (Julien Schmidt)** - JINNOUCHI Yasushi <delphinus@remora.cx>
- gofmt the code examples - James Pettyjohn <japettyjohn@users.noreply.github.com>
- Jamie Stackhouse <jamie.stackhouse@redspace.com>
- Jason Lee <jawc@hotmail.com>
**@kelcecil (Kel Cecil)** - Javier Provecho <j.provecho@dartekstudios.com>
- Fix readme typo - Javier Provecho <javier.provecho@bq.com>
- Javier Provecho <javiertitan@gmail.com>
- Javier Provecho Fernandez <j.provecho@dartekstudios.com>
**@kyledinh (Kyle Dinh)** - Javier Provecho Fernandez <javiertitan@gmail.com>
- Adds RunTLS() - Jean-Christophe Lebreton <jclebreton@gmail.com>
- Jeff <laojianzi1994@gmail.com>
- Jeremy Loy <jeremy.b.loy@icloud.com>
**@LinusU (Linus Unnebäck)** - Jim Filippou <p3160253@aueb.gr>
- Small fixes in README - Jimmy Pettersson <jimmy@expertmaker.com>
- John Bampton <jbampton@users.noreply.github.com>
- Johnny Dallas <johnnydallas0308@gmail.com>
**@loongmxbt (Saint Asky)** - Johnny Dallas <theonlyjohnny@theonlyjohnny.sh>
- Fix typo in example - Jonathan (JC) Chen <jc@dijonkitchen.org>
- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com>
- Josh Horowitz <joshua.m.horowitz@gmail.com>
**@lucas-clemente (Lucas Clemente)** - Joshua Loper <josh.el3@gmail.com>
- ★ work around path.Join removing trailing slashes from routes - Julien Schmidt <github@julienschmidt.com>
- Jun Kimura <jksmphone@gmail.com>
- Justin Beckwith <justin.beckwith@gmail.com>
**@mattn (Yasuhiro Matsumoto)** - Justin Israel <justinisrael@gmail.com>
- Improve color logger - Justin Mayhew <mayhew@live.ca>
- Jérôme Laforge <jerome-laforge@users.noreply.github.com>
- Kacper Bąk <56700396+53jk1@users.noreply.github.com>
**@mdigger (Dmitry Sedykh)** - Kamron Batman <kamronbatman@users.noreply.github.com>
- Fixes Form binding when content-type is x-www-form-urlencoded - Kane Rogers <kane@cleanstream.com.au>
- No repeat call c.Writer.Status() in gin.Logger - Kaushik Neelichetty <kaushikneelichetty6132@gmail.com>
- Fixes Content-Type for json render - Keiji Yoshida <yoshida.keiji.84@gmail.com>
- Kel Cecil <kel.cecil@listhub.com>
- Kevin Mulvey <kmulvey@linux.com>
**@mirzac (Mirza Ceric)** - Kevin Zhu <ipandtcp@gmail.com>
- Fix debug printing - Kirill Motkov <motkov.kirill@gmail.com>
- Klemen Sever <ksever@student.42.fr>
- Kristoffer A. Iversen <kristoffer.a.iversen@gmail.com>
**@mopemope (Yutaka Matsubara)** - Krzysztof Szafrański <k.p.szafranski@gmail.com>
- ★ Adds Godep support (Dependencies Manager) - Kumar McMillan <kumar.mcmillan@gmail.com>
- Fix variadic parameter in the flexible render API - Kyle Mcgill <email@kylescottmcgill.com>
- Fix Corrupted plain render - Lanco <35420416+lancoLiu@users.noreply.github.com>
- Add Pluggable View Renderer Example - Levi Olson <olson.levi@gmail.com>
- Lin Kao-Yuan <mosdeo@gmail.com>
- Linus Unnebäck <linus@folkdatorn.se>
**@msemenistyi (Mykyta Semenistyi)** - Lucas Clemente <lucas@clemente.io>
- update Readme.md. Add code to String method - Ludwig Valda Vasquez <bredov@gmail.com>
- Luis GG <lggomez@users.noreply.github.com>
- MW Lim <williamchange@gmail.com>
**@msoedov (Sasha Myasoedov)** - Maksimov Sergey <konjoot@gmail.com>
- ★ Adds tons of unit tests. - Manjusaka <lizheao940510@gmail.com>
- Manu MA <manu.mtza@gmail.com>
- Manu MA <manu.valladolid@gmail.com>
**@ngerakines (Nick Gerakines)** - Manu Mtz-Almeida <manu.valladolid@gmail.com>
- ★ Improves API, c.GET() doesn't panic - Manu Mtz.-Almeida <manu.valladolid@gmail.com>
- Adds MustGet() method - Manuel Alonso <manuelalonso@invisionapp.com>
- Mara Kim <hacker.root@gmail.com>
- Mario Kostelac <mario@intercom.io>
**@r8k (Rajiv Kilaparti)** - Martin Karlsch <martin@karlsch.com>
- Fix Port usage in README. - Matt Newberry <mnewberry@opentable.com>
- Matt Williams <gh@mattyw.net>
- Matthieu MOREL <mmorel-35@users.noreply.github.com>
**@rayrod2030 (Ray Rodriguez)** - Max Hilbrunner <mhilbrunner@users.noreply.github.com>
- Fix typo in example - Maxime Soulé <btik-git@scoubidou.com>
- MetalBreaker <johnymichelson@gmail.com>
- Michael Puncel <mpuncel@squareup.com>
**@rns** - MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com>
- Fix typo in example - Mike <38686456+icy4ever@users.noreply.github.com>
- Mike Stipicevic <mst@ableton.com>
- Miki Tebeka <miki.tebeka@gmail.com>
**@RobAWilkinson (Robert Wilkinson)** - Miles <MilesLin@users.noreply.github.com>
- Add example of forms and params - Mirza Ceric <mirza.ceric@b2match.com>
- Mykyta Semenistyi <nikeiwe@gmail.com>
- Naoki Takano <honten@tinkermode.com>
**@rogierlommers (Rogier Lommers)** - Ngalim Siregar <ngalim.siregar@gmail.com>
- Add updated static serve example - Ni Hao <supernihaooo@qq.com>
- Nick Gerakines <nick@gerakines.net>
- Nikifor Seryakov <nikandfor@gmail.com>
**@se77en (Damon Zhao)** - Notealot <714804968@qq.com>
- Improve color logging - Olivier Mengué <dolmen@cpan.org>
- Olivier Robardet <orobardet@users.noreply.github.com>
- Pablo Moncada <pablo.moncada@bq.com>
**@silasb (Silas Baronda)** - Pablo Moncada <pmoncadaisla@gmail.com>
- Fixing quotes in README - Panmax <967168@qq.com>
- Peperoncino <2wua4nlyi@gmail.com>
- Philipp Meinen <philipp@bind.ch>
**@SkuliOskarsson (Skuli Oskarsson)** - Pierre Massat <pierre@massat.io>
- Fixes some texts in README II - Qt <golang.chen@gmail.com>
- Quentin ROYER <aydendevg@gmail.com>
- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com>
**@slimmy (Jimmy Pettersson)** - Rafal Zajac <rzajac@gmail.com>
- Added messages for required bindings - Rahul Datta Roy <rahuldroy@users.noreply.github.com>
- Rajiv Kilaparti <rajivk085@gmail.com>
- Raphael Gavache <raphael.gavache@datadoghq.com>
**@smira (Andrey Smirnov)** - Ray Rodriguez <rayrod2030@gmail.com>
- Add support for ignored/unexported fields in binding - Regner Blok-Andersen <shadowdf@gmail.com>
- Remco <remco@dutchcoders.io>
- Rex Lee(李俊) <duguying2008@gmail.com>
**@superalsrk (SRK.Lyu)** - Richard Lee <dlackty@gmail.com>
- Update httprouter godeps - Riverside <wangyb65@gmail.com>
- Robert Wilkinson <wilkinson.robert.a@gmail.com>
- Rogier Lommers <rogier@lommers.org>
**@tebeka (Miki Tebeka)** - Rohan Pai <me@rohanpai.com>
- Use net/http constants instead of numeric values - Romain Beuque <rbeuque74@gmail.com>
- Roman Belyakovsky <ihryamzik@gmail.com>
- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com>
**@techjanitor** - Roman Zaynetdinov <roman.zaynetdinov@lekane.com>
- Update context.go reserved IPs - Ronald Petty <ronald.petty@rx-m.com>
- Ross Wolf <31489089+rw-access@users.noreply.github.com>
- Roy Lou <roylou@gmail.com>
**@yosssi (Keiji Yoshida)** - Rubi <14269809+codenoid@users.noreply.github.com>
- Fix link in README - Ryan <46182144+ryanker@users.noreply.github.com>
- Ryan J. Yoder <me@ryanjyoder.com>
- SRK.Lyu <superalsrk@gmail.com>
**@yuyabee** - Sai <sairoutine@gmail.com>
- Fixed README - Samuel Abreu <sdepaula@gmail.com>
- Santhosh Kumar <santhoshkumarr1096@gmail.com>
- Sasha Melentyev <sasha@melentyev.io>
- Sasha Myasoedov <msoedov@gmail.com>
- Segev Finer <segev208@gmail.com>
- Sergey Egorov <egorovhome@gmail.com>
- Sergey Fedchenko <seregayoga@bk.ru>
- Sergey Gonimar <sergey.gonimar@gmail.com>
- Sergey Ponomarev <me@sergey-ponomarev.ru>
- Serica <943914044@qq.com>
- Shamus Taylor <Shamus03@me.com>
- Shilin Wang <jarvisfironman@gmail.com>
- Shuo <openset.wang@gmail.com>
- Skuli Oskarsson <skuli@codeiak.io>
- Snawoot <vladislav-ex-github@vm-0.com>
- Sridhar Ratnakumar <srid@srid.ca>
- Steeve Chailloux <steeve@chaahk.com>
- Sudhir Mishra <sudhirxps@gmail.com>
- Suhas Karanth <sudo-suhas@users.noreply.github.com>
- TaeJun Park <miking38@gmail.com>
- Tatsuya Hoshino <tatsuya7.hoshino7@gmail.com>
- Tevic <tevic.tt@gmail.com>
- Tevin Jeffrey <tev.jeffrey@gmail.com>
- The Gitter Badger <badger@gitter.im>
- Thibault Jamet <tjamet@users.noreply.github.com>
- Thomas Boerger <thomas@webhippie.de>
- Thomas Schaffer <loopfz@gmail.com>
- Tommy Chu <tommychu2256@gmail.com>
- Tudor Roman <tudurom@gmail.com>
- Uwe Dauernheim <djui@users.noreply.github.com>
- Valentine Oragbakosi <valentine13400@gmail.com>
- Vas N <pnvasanth@users.noreply.github.com>
- Vasilyuk Vasiliy <By-Vasiliy@users.noreply.github.com>
- Victor Castell <victor@victorcastell.com>
- Vince Yuan <vince.yuan@gmail.com>
- Vyacheslav Dubinin <vyacheslav.dubinin@gmail.com>
- Waynerv <ampedee@gmail.com>
- Weilin Shi <934587911@qq.com>
- Xudong Cai <fifsky@gmail.com>
- Yasuhiro Matsumoto <mattn.jp@gmail.com>
- Yehezkiel Syamsuhadi <ybs@ybs.im>
- Yoshiki Nakagawa <yyoshiki41@gmail.com>
- Yoshiyuki Kinjo <yskkin+github@gmail.com>
- Yue Yang <g1enyy0ung@gmail.com>
- ZYunH <zyunhjob@163.com>
- Zach Newburgh <zach.newburgh@gmail.com>
- Zasda Yusuf Mikail <zasdaym@gmail.com>
- ZhangYunHao <zyunhjob@163.com>
- ZhiFeng Hu <hufeng1987@gmail.com>
- Zhu Xi <zhuxi910511@163.com>
- a2tt <usera2tt@gmail.com>
- ahuigo <1781999+ahuigo@users.noreply.github.com>
- ali <anio@users.noreply.github.com>
- aljun <salameryy@163.com>
- andrea <crypto.andrea@protonmail.ch>
- andriikushch <andrii.kushch@gmail.com>
- anoty <anjunyou@foxmail.com>
- awkj <hzzbiu@gmail.com>
- axiaoxin <254606826@qq.com>
- bbiao <bbbiao@gmail.com>
- bestgopher <84328409@qq.com>
- betahu <zhong.wenhuang@foxmail.com>
- bigwheel <k.bigwheel+eng@gmail.com>
- bn4t <17193640+bn4t@users.noreply.github.com>
- bullgare <bullgare@gmail.com>
- chainhelen <chainhelen@gmail.com>
- chenyang929 <chenyang929code@gmail.com>
- chriswhelix <chris.williams@helix.com>
- collinmsn <4130944@qq.com>
- cssivision <cssivision@gmail.com>
- danielalves <alves.lopes.dan@gmail.com>
- delphinus <delphinus@remora.cx>
- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- dickeyxxx <jeff@dickeyxxx.com>
- edebernis <emeric.debernis@gmail.com>
- error10 <error@ioerror.us>
- esplo <esplo@users.noreply.github.com>
- eudore <30709860+eudore@users.noreply.github.com>
- ffhelicopter <32922889+ffhelicopter@users.noreply.github.com>
- filikos <11477309+filikos@users.noreply.github.com>
- forging2012 <forging2012@users.noreply.github.com>
- goqihoo <goqihoo@gmail.com>
- grapeVine <treeui.old@gmail.com>
- guonaihong <guonaihong@qq.com>
- heige <daheige@users.noreply.github.com>
- heige <zhuwei313@hotmail.com>
- hellojukay <hellojukay@163.com>
- henrylee2cn <henrylee2cn@gmail.com>
- htobenothing <htobenothing@gmail.com>
- iamhesir <78344375+iamhesir@users.noreply.github.com>
- ijaa <kailiu2013@gmail.com>
- ishanray <ishan.iipm@gmail.com>
- ishanray <ishanray@users.noreply.github.com>
- itcloudy <272685110@qq.com>
- jarodsong6 <jarodsong6@gmail.com>
- jasonrhansen <jasonrodneyhansen@gmail.com>
- jincheng9 <perfume0607@gmail.com>
- joeADSP <75027008+joeADSP@users.noreply.github.com>
- junfengye <junfeng.yejf@gmail.com>
- kaiiak <aNxFi37X@outlook.com>
- kebo <kevinke2020@outlook.com>
- keke <19yamashita15@gmail.com>
- kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com>
- kyledinh <kyledinh@gmail.com>
- lantw44 <lantw44@gmail.com>
- likakuli <1154584512@qq.com>
- linfangrong <linfangrong.liuxin@qq.com>
- linzi <873804682@qq.com>
- llgoer <yanghuxiao@vip.qq.com>
- long-road <13412081338@163.com>
- mbesancon <mathieu.besancon@gmail.com>
- mehdy <mehdy.khoshnoody@gmail.com>
- metal A-wing <freedom.awing.777@gmail.com>
- micanzhang <micanzhang@gmail.com>
- minarc <ragnhildmowinckel@gmail.com>
- mllu <mornlyn@gmail.com>
- mopemoepe <yutaka.matsubara@gmail.com>
- msoedov <msoedov@gmail.com>
- mstmdev <mstmdev@gmail.com>
- novaeye <fcoffee@gmail.com>
- olebedev <oolebedev@gmail.com>
- phithon <phith0n@users.noreply.github.com>
- pjgg <pablo.gonzalez.granados@gmail.com>
- qm012 <67568757+qm012@users.noreply.github.com>
- raymonder jin <rayjingithub@gmail.com>
- rns <ruslan.shvedov@gmail.com>
- root@andrea:~# <crypto.andrea@protonmail.ch>
- sekky0905 <20237968+sekky0905@users.noreply.github.com>
- senhtry <w169q169@gmail.com>
- shadrus <shadrus@gmail.com>
- silasb <silas.baronda@gmail.com>
- solos <lxl1217@gmail.com>
- songjiayang <songjiayang@users.noreply.github.com>
- sope <shenshouer@163.com>
- srt180 <30768686+srt180@users.noreply.github.com>
- stackerzzq <foo_stacker@yeah.net>
- sunshineplan <sunshineplan@users.noreply.github.com>
- syssam <s.y.s.sam.sys@gmail.com>
- techjanitor <puntme@gmail.com>
- techjanitor <techjanitor@users.noreply.github.com>
- thinkerou <thinkerou@gmail.com>
- thinkgo <49174849+thinkgos@users.noreply.github.com>
- tsirolnik <tsirolnik@users.noreply.github.com>
- tyltr <31768692+tylitianrui@users.noreply.github.com>
- vinhha96 <anhvinha1@gmail.com>
- voidman <retmain@foxmail.com>
- vz <vzvway@gmail.com>
- wei <wei840222@gmail.com>
- weibaohui <weibaohui@yeah.net>
- whirosan <whirosan@users.noreply.github.com>
- willnewrelic <will@newrelic.com>
- wssccc <wssccc@qq.com>
- wuhuizuo <wuhuizuo@126.com>
- xyb <xyb4638@gmail.com>
- y-yagi <yuuji.yaginuma@gmail.com>
- yiranzai <wuqingdzx@gmail.com>
- youzeliang <youzel@126.com>
- yugu <chenzilong_1227@foxmail.com>
- yuyabe <yuyabee@gmail.com>
- zebozhuang <zebozhuang@163.com>
- zero11-0203 <93071220+zero11-0203@users.noreply.github.com>
- zesani <7sin@outlook.co.th>
- zhanweidu <zhanweidu@163.com>
- zhing <zqwillseven@gmail.com>
- ziheng <zihenglv@gmail.com>
- zzjin <zzjin@users.noreply.github.com>
- 森 優太 <59682979+uta-mori@users.noreply.github.com>
- 杰哥 <858806258@qq.com>
- 涛叔 <hi@taoshu.in>
- 市民233 <mengrenxiong@gmail.com>
- 尹宝强 <wmdandme@gmail.com>
- 梦溪笔谈 <loongmxbt@gmail.com>
- 飞雪无情 <ls8707@gmail.com>
- 寻寻觅觅的Gopher <zoujh99@qq.com>

View File

@ -1,11 +1,11 @@
# Benchmark System # Benchmark System
**VM HOST:** Travis **VM HOST:** Travis
**Machine:** Ubuntu 16.04.6 LTS x64 **Machine:** Ubuntu 16.04.6 LTS x64
**Date:** May 04th, 2020 **Date:** May 04th, 2020
**Version:** Gin v1.6.3 **Version:** Gin v1.6.3
**Go Version:** 1.14.2 linux/amd64 **Go Version:** 1.14.2 linux/amd64
**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark) **Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061) **Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061)

View File

@ -1,5 +1,134 @@
# Gin ChangeLog # Gin ChangeLog
## Gin v1.8.1
### ENHANCEMENTS
* feat(context): add ContextWithFallback feature flag [#3172](https://github.com/gin-gonic/gin/pull/3172)
## Gin v1.8.0
## Break Changes
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP`
* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751)
### BUGFIXES
* Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861)
* Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)
* Fix: missing sameSite when do context.reset() [#3123](https://github.com/gin-gonic/gin/pull/3123)
### ENHANCEMENTS
* Use Header() instead of deprecated HeaderMap [#2694](https://github.com/gin-gonic/gin/pull/2694)
* RouterGroup.Handle regular match optimization of http method [#2685](https://github.com/gin-gonic/gin/pull/2685)
* Add support go-json, another drop-in json replacement [#2680](https://github.com/gin-gonic/gin/pull/2680)
* Use errors.New to replace fmt.Errorf will much better [#2707](https://github.com/gin-gonic/gin/pull/2707)
* Use Duration.Truncate for truncating precision [#2711](https://github.com/gin-gonic/gin/pull/2711)
* Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723)
* Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files)
* Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737)
* Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765)
* Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720)
* Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787)
* Fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() [#2769](https://github.com/gin-gonic/gin/pull/2769)
* Some codes optimize [#2830](https://github.com/gin-gonic/gin/pull/2830) [#2834](https://github.com/gin-gonic/gin/pull/2834) [#2838](https://github.com/gin-gonic/gin/pull/2838) [#2837](https://github.com/gin-gonic/gin/pull/2837) [#2788](https://github.com/gin-gonic/gin/pull/2788) [#2848](https://github.com/gin-gonic/gin/pull/2848) [#2851](https://github.com/gin-gonic/gin/pull/2851) [#2701](https://github.com/gin-gonic/gin/pull/2701)
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967)
* Test(route): expose performRequest func [#3012](https://github.com/gin-gonic/gin/pull/3012)
* Support h2c with prior knowledge [#1398](https://github.com/gin-gonic/gin/pull/1398)
* Feat attachment filename support utf8 [#3071](https://github.com/gin-gonic/gin/pull/3071)
* Feat: add StaticFileFS [#2749](https://github.com/gin-gonic/gin/pull/2749)
* Feat(context): return GIN Context from Value method [#2825](https://github.com/gin-gonic/gin/pull/2825)
* Feat: automatically SetMode to TestMode when run go test [#3139](https://github.com/gin-gonic/gin/pull/3139)
* Add TOML bining for gin [#3081](https://github.com/gin-gonic/gin/pull/3081)
* IPv6 add default trusted proxies [#3033](https://github.com/gin-gonic/gin/pull/3033)
### DOCS
* Add note about nomsgpack tag to the readme [#2703](https://github.com/gin-gonic/gin/pull/2703)
## Gin v1.7.7
### BUGFIXES
* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).
* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).
* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).
* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).
### ENHANCEMENTS
* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).
* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).
### DOCS
* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).
## Gin v1.7.6
### BUGFIXES
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
## Gin v1.7.4
### BUGFIXES
* bump new release to fix checksum mismatch
## Gin v1.7.3
### BUGFIXES
* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)
## Gin v1.7.2
### BUGFIXES
* Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).
## Gin v1.7.1
### BUGFIXES
* fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675))
## Gin v1.7.0
### BUGFIXES
* fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))
* fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))
* fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366))
### ENHANCEMENTS
* Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663))
* chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365))
* Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368))
* chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375))
* chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
* Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
* update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
* Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
* Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
* binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
* Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412))
* Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487))
* update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512))
* reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508))
* implement ".Unwrap() error" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526))
* Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484))
* chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371))
* Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302))
* basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609))
* Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663))
* feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632))
## Gin v1.6.3 ## Gin v1.6.3
### ENHANCEMENTS ### ENHANCEMENTS
@ -215,12 +344,12 @@
## Gin 1.1 ## Gin 1.1
- [NEW] Implement QueryArray and PostArray methods - [NEW] Implement QueryArray and PostArray methods
- [NEW] Refactor GetQuery and GetPostForm - [NEW] Refactor GetQuery and GetPostForm
- [NEW] Add contribution guide - [NEW] Add contribution guide
- [FIX] Corrected typos in README - [FIX] Corrected typos in README
- [FIX] Removed additional Iota - [FIX] Removed additional Iota
- [FIX] Changed imports to gopkg instead of github in README (#733) - [FIX] Changed imports to gopkg instead of github in README (#733)
- [FIX] Logger: skip ANSI color commands if output is not a tty - [FIX] Logger: skip ANSI color commands if output is not a tty
## Gin 1.0rc2 (...) ## Gin 1.0rc2 (...)

View File

@ -1,4 +1,4 @@
## Contributing ## Contributing
- With issues: - With issues:
- Use the search tool before opening a new issue. - Use the search tool before opening a new issue.
@ -8,6 +8,6 @@
- With pull requests: - With pull requests:
- Open your pull request against `master` - Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them. - Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integration systems such as TravisCI. - It should pass all tests in the available continuous integration systems such as GitHub Actions.
- You should add/modify tests to cover your proposed code changes. - You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README. - If your pull request contains a new feature, please document it on the README.

View File

@ -1,5 +1,6 @@
GO ?= go GO ?= go
GOFMT ?= gofmt "-s" GOFMT ?= gofmt "-s"
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
PACKAGES ?= $(shell $(GO) list ./...) PACKAGES ?= $(shell $(GO) list ./...)
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
GOFILES := $(shell find . -name "*.go") GOFILES := $(shell find . -name "*.go")
@ -67,5 +68,10 @@ misspell:
.PHONY: tools .PHONY: tools
tools: tools:
go install golang.org/x/lint/golint; \ @if [ $(GO_VERSION) -gt 15 ]; then \
go install github.com/client9/misspell/cmd/misspell; $(GO) install golang.org/x/lint/golint@latest; \
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
elif [ $(GO_VERSION) -lt 16 ]; then \
$(GO) install golang.org/x/lint/golint; \
$(GO) install github.com/client9/misspell/cmd/misspell; \
fi

312
README.md
View File

@ -2,7 +2,7 @@
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png"> <img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster)
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
[![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
@ -23,7 +23,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Quick start](#quick-start) - [Quick start](#quick-start)
- [Benchmarks](#benchmarks) - [Benchmarks](#benchmarks)
- [Gin v1. stable](#gin-v1-stable) - [Gin v1. stable](#gin-v1-stable)
- [Build with jsoniter](#build-with-jsoniter) - [Build with jsoniter/go-json](#build-with-json-replacement)
- [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
- [API Examples](#api-examples) - [API Examples](#api-examples)
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
- [Parameters in path](#parameters-in-path) - [Parameters in path](#parameters-in-path)
@ -77,6 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [http2 server push](#http2-server-push) - [http2 server push](#http2-server-push)
- [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Define format for the log of routes](#define-format-for-the-log-of-routes)
- [Set and get a cookie](#set-and-get-a-cookie) - [Set and get a cookie](#set-and-get-a-cookie)
- [Don't trust all proxies](#dont-trust-all-proxies)
- [Testing](#testing) - [Testing](#testing)
- [Users](#users) - [Users](#users)
@ -84,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
To install Gin package, you need to install Go and set your Go workspace first. To install Gin package, you need to install Go and set your Go workspace first.
1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin. 1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin.
```sh ```sh
$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-gonic/gin
@ -103,7 +105,7 @@ import "net/http"
``` ```
## Quick start ## Quick start
```sh ```sh
# assume the following codes in example.go file # assume the following codes in example.go file
$ cat example.go $ cat example.go
@ -112,12 +114,16 @@ $ cat example.go
```go ```go
package main package main
import "github.com/gin-gonic/gin" import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() { func main() {
r := gin.Default() r := gin.Default()
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "pong", "message": "pong",
}) })
}) })
@ -182,13 +188,28 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
- [x] Battle tested. - [x] Battle tested.
- [x] API frozen, new releases will not break your code. - [x] API frozen, new releases will not break your code.
## Build with [jsoniter](https://github.com/json-iterator/go) ## Build with json replacement
Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. Gin uses `encoding/json` as default json package but you can change it by build from other tags.
[jsoniter](https://github.com/json-iterator/go)
```sh ```sh
$ go build -tags=jsoniter . $ go build -tags=jsoniter .
``` ```
[go-json](https://github.com/goccy/go-json)
```sh
$ go build -tags=go_json .
```
## Build without `MsgPack` rendering feature
Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
```sh
$ go build -tags=nomsgpack .
```
This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
## API Examples ## API Examples
@ -240,7 +261,15 @@ func main() {
// For each matched request Context will hold the route definition // For each matched request Context will hold the route definition
router.POST("/user/:name/*action", func(c *gin.Context) { router.POST("/user/:name/*action", func(c *gin.Context) {
c.FullPath() == "/user/:name/*action" // true b := c.FullPath() == "/user/:name/*action" // true
c.String(http.StatusOK, "%t", b)
})
// This handler will add a new router for /user/groups.
// Exact routes are resolved before param routes, regardless of the order they were defined.
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
router.GET("/user/groups", func(c *gin.Context) {
c.String(http.StatusOK, "The available groups are [...]")
}) })
router.Run(":8080") router.Run(":8080")
@ -275,7 +304,7 @@ func main() {
message := c.PostForm("message") message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous") nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": "posted", "status": "posted",
"message": message, "message": message,
"nick": nick, "nick": nick,
@ -359,7 +388,7 @@ func main() {
// Set a lower memory limit for multipart forms (default is 32 MiB) // Set a lower memory limit for multipart forms (default is 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) { router.POST("/upload", func(c *gin.Context) {
// single file // Single file
file, _ := c.FormFile("file") file, _ := c.FormFile("file")
log.Println(file.Filename) log.Println(file.Filename)
@ -488,6 +517,7 @@ func main() {
// nested group // nested group
testing := authorized.Group("testing") testing := authorized.Group("testing")
// visit 0.0.0.0:8080/testing/analytics
testing.GET("/analytics", analyticsEndpoint) testing.GET("/analytics", analyticsEndpoint)
} }
@ -544,7 +574,7 @@ func main() {
router := gin.Default() router := gin.Default()
router.GET("/ping", func(c *gin.Context) { router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
   router.Run(":8080")    router.Run(":8080")
@ -576,7 +606,7 @@ func main() {
router.Use(gin.Recovery()) router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) { router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
router.Run(":8080") router.Run(":8080")
@ -588,51 +618,51 @@ func main() {
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
``` ```
### Controlling Log output coloring ### Controlling Log output coloring
By default, logs output on console should be colorized depending on the detected TTY. By default, logs output on console should be colorized depending on the detected TTY.
Never colorize logs: Never colorize logs:
```go ```go
func main() { func main() {
// Disable log's color // Disable log's color
gin.DisableConsoleColor() gin.DisableConsoleColor()
// Creates a gin router with default middleware: // Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware // logger and recovery (crash-free) middleware
router := gin.Default() router := gin.Default()
router.GET("/ping", func(c *gin.Context) { router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
router.Run(":8080") router.Run(":8080")
} }
``` ```
Always colorize logs: Always colorize logs:
```go ```go
func main() { func main() {
// Force log's color // Force log's color
gin.ForceConsoleColor() gin.ForceConsoleColor()
// Creates a gin router with default middleware: // Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware // logger and recovery (crash-free) middleware
router := gin.Default() router := gin.Default()
router.GET("/ping", func(c *gin.Context) { router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
router.Run(":8080") router.Run(":8080")
} }
``` ```
### Model binding and validation ### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
@ -640,10 +670,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
Also, Gin provides two sets of methods for binding: Also, Gin provides two sets of methods for binding:
- **Type** - Must bind - **Type** - Must bind
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader` - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
- **Type** - Should bind - **Type** - Should bind
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader` - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
@ -667,19 +697,19 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if json.User != "manu" || json.Password != "123" { if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return return
} }
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
// Example for binding XML ( // Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?> // <?xml version="1.0" encoding="UTF-8"?>
// <root> // <root>
// <user>user</user> // <user>manu</user>
// <password>123</password> // <password>123</password>
// </root>) // </root>)
router.POST("/loginXML", func(c *gin.Context) { router.POST("/loginXML", func(c *gin.Context) {
@ -688,12 +718,12 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if xml.User != "manu" || xml.Password != "123" { if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return return
} }
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
@ -705,12 +735,12 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if form.User != "manu" || form.Password != "123" { if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return return
} }
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
@ -807,7 +837,7 @@ $ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" $ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}% {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
``` ```
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
@ -822,6 +852,7 @@ package main
import ( import (
"log" "log"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -844,7 +875,7 @@ func startPage(c *gin.Context) {
log.Println(person.Name) log.Println(person.Name)
log.Println(person.Address) log.Println(person.Address)
} }
c.String(200, "Success") c.String(http.StatusOK, "Success")
} }
``` ```
@ -858,6 +889,7 @@ package main
import ( import (
"log" "log"
"net/http"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -881,7 +913,7 @@ func startPage(c *gin.Context) {
var person Person var person Person
// If `GET`, only `Form` binding engine (`query`) used. // If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
if c.ShouldBind(&person) == nil { if c.ShouldBind(&person) == nil {
log.Println(person.Name) log.Println(person.Name)
log.Println(person.Address) log.Println(person.Address)
@ -890,7 +922,7 @@ func startPage(c *gin.Context) {
log.Println(person.UnixTime) log.Println(person.UnixTime)
} }
c.String(200, "Success") c.String(http.StatusOK, "Success")
} }
``` ```
@ -906,7 +938,11 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/846).
```go ```go
package main package main
import "github.com/gin-gonic/gin" import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct { type Person struct {
ID string `uri:"id" binding:"required,uuid"` ID string `uri:"id" binding:"required,uuid"`
@ -918,10 +954,10 @@ func main() {
route.GET("/:name/:id", func(c *gin.Context) { route.GET("/:name/:id", func(c *gin.Context) {
var person Person var person Person
if err := c.ShouldBindUri(&person); err != nil { if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err}) c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
return return
} }
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
}) })
route.Run(":8088") route.Run(":8088")
} }
@ -940,6 +976,8 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -954,11 +992,11 @@ func main() {
h := testHeader{} h := testHeader{}
if err := c.ShouldBindHeader(&h); err != nil { if err := c.ShouldBindHeader(&h); err != nil {
c.JSON(200, err) c.JSON(http.StatusOK, err)
} }
fmt.Printf("%#v\n", h) fmt.Printf("%#v\n", h)
c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
}) })
r.Run() r.Run()
@ -988,7 +1026,7 @@ type myForm struct {
func formHandler(c *gin.Context) { func formHandler(c *gin.Context) {
var fakeForm myForm var fakeForm myForm
c.ShouldBind(&fakeForm) c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors}) c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
} }
... ...
@ -1145,7 +1183,7 @@ func main() {
data := gin.H{ data := gin.H{
"foo": "bar", "foo": "bar",
} }
//callback is x //callback is x
// Will output : x({\"foo\":\"bar\"}) // Will output : x({\"foo\":\"bar\"})
c.JSONP(http.StatusOK, data) c.JSONP(http.StatusOK, data)
@ -1190,21 +1228,21 @@ This feature is unavailable in Go 1.6 and lower.
```go ```go
func main() { func main() {
r := gin.Default() r := gin.Default()
// Serves unicode entities // Serves unicode entities
r.GET("/json", func(c *gin.Context) { r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"html": "<b>Hello, world!</b>", "html": "<b>Hello, world!</b>",
}) })
}) })
// Serves literal characters // Serves literal characters
r.GET("/purejson", func(c *gin.Context) { r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{ c.PureJSON(http.StatusOK, gin.H{
"html": "<b>Hello, world!</b>", "html": "<b>Hello, world!</b>",
}) })
}) })
// listen and serve on 0.0.0.0:8080 // listen and serve on 0.0.0.0:8080
r.Run(":8080") r.Run(":8080")
} }
@ -1218,7 +1256,8 @@ func main() {
router.Static("/assets", "./assets") router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico") router.StaticFile("/favicon.ico", "./resources/favicon.ico")
router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
router.Run(":8080") router.Run(":8080")
} }
@ -1384,7 +1423,7 @@ import (
func formatAsDate(t time.Time) string { func formatAsDate(t time.Time) string {
year, month, day := t.Date() year, month, day := t.Date()
return fmt.Sprintf("%d%02d/%02d", year, month, day) return fmt.Sprintf("%d/%02d/%02d", year, month, day)
} }
func main() { func main() {
@ -1446,7 +1485,7 @@ r.GET("/test", func(c *gin.Context) {
r.HandleContext(c) r.HandleContext(c)
}) })
r.GET("/test2", func(c *gin.Context) { r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"}) c.JSON(http.StatusOK, gin.H{"hello": "world"})
}) })
``` ```
@ -1608,6 +1647,7 @@ package main
import ( import (
"log" "log"
"net/http"
"github.com/gin-gonic/autotls" "github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -1618,7 +1658,7 @@ func main() {
// Ping handler // Ping handler
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
log.Fatal(autotls.Run(r, "example1.com", "example2.com")) log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
@ -1632,6 +1672,7 @@ package main
import ( import (
"log" "log"
"net/http"
"github.com/gin-gonic/autotls" "github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -1643,7 +1684,7 @@ func main() {
// Ping handler // Ping handler
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
m := autocert.Manager{ m := autocert.Manager{
@ -1802,8 +1843,8 @@ func main() {
// Initializing the server in a goroutine so that // Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below // it won't block the graceful shutdown handling below
go func() { go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err) log.Printf("listen: %s\n", err)
} }
}() }()
@ -1812,7 +1853,7 @@ func main() {
quit := make(chan os.Signal) quit := make(chan os.Signal)
// kill (no param) default send syscall.SIGTERM // kill (no param) default send syscall.SIGTERM
// kill -2 is syscall.SIGINT // kill -2 is syscall.SIGINT
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
log.Println("Shutting down server...") log.Println("Shutting down server...")
@ -1821,10 +1862,11 @@ func main() {
// the request it is currently handling // the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err) log.Fatal("Server forced to shutdown:", err)
} }
log.Println("Server exiting") log.Println("Server exiting")
} }
``` ```
@ -1903,7 +1945,7 @@ type StructD struct {
func GetDataB(c *gin.Context) { func GetDataB(c *gin.Context) {
var b StructB var b StructB
c.Bind(&b) c.Bind(&b)
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"a": b.NestedStruct, "a": b.NestedStruct,
"b": b.FieldB, "b": b.FieldB,
}) })
@ -1912,7 +1954,7 @@ func GetDataB(c *gin.Context) {
func GetDataC(c *gin.Context) { func GetDataC(c *gin.Context) {
var b StructC var b StructC
c.Bind(&b) c.Bind(&b)
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"a": b.NestedStructPointer, "a": b.NestedStructPointer,
"c": b.FieldC, "c": b.FieldC,
}) })
@ -1921,7 +1963,7 @@ func GetDataC(c *gin.Context) {
func GetDataD(c *gin.Context) { func GetDataD(c *gin.Context) {
var b StructD var b StructD
c.Bind(&b) c.Bind(&b)
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"x": b.NestedAnonyStruct, "x": b.NestedAnonyStruct,
"d": b.FieldD, "d": b.FieldD,
}) })
@ -1984,7 +2026,7 @@ func SomeHandler(c *gin.Context) {
objA := formA{} objA := formA{}
objB := formB{} objB := formB{}
// This reads c.Request.Body and stores the result into the context. // This reads c.Request.Body and stores the result into the context.
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
c.String(http.StatusOK, `the body should be formA`) c.String(http.StatusOK, `the body should be formA`)
// At this time, it reuses body stored in the context. // At this time, it reuses body stored in the context.
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
@ -2006,6 +2048,61 @@ enough to call binding at once.
can be called by `c.ShouldBind()` multiple times without any damage to can be called by `c.ShouldBind()` multiple times without any damage to
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
### Bind form-data request with custom struct and custom tag
```go
const (
customerTag = "url"
defaultMemory = 32 << 20
)
type customerBinding struct {}
func (customerBinding) Name() string {
return "form"
}
func (customerBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
return err
}
return validate(obj)
}
func validate(obj interface{}) error {
if binding.Validator == nil {
return nil
}
return binding.Validator.ValidateStruct(obj)
}
// Now we can do this!!!
// FormA is a external type that we can't modify it's tag
type FormA struct {
FieldA string `url:"field_a"`
}
func ListHandler(s *Service) func(ctx *gin.Context) {
return func(ctx *gin.Context) {
var urlBinding = customerBinding{}
var opt FormA
err := ctx.MustBindWith(&opt, urlBinding)
if err != nil {
...
}
...
}
}
```
### http2 server push ### http2 server push
http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
@ -2016,6 +2113,7 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -2044,7 +2142,7 @@ func main() {
log.Printf("Failed to push: %v", err) log.Printf("Failed to push: %v", err)
} }
} }
c.HTML(200, "https", gin.H{ c.HTML(http.StatusOK, "https", gin.H{
"status": "success", "status": "success",
}) })
}) })
@ -2125,6 +2223,73 @@ func main() {
} }
``` ```
## Don't trust all proxies
Gin lets you specify which headers to hold the real client IP (if any),
as well as specifying which proxies (or direct clients) you trust to
specify one of these headers.
Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
or network CIDRs from where clients which their request headers related to client
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
IPv6 CIDRs.
**Attention:** Gin trust all proxies by default if you don't specify a trusted
proxy using the function above, **this is NOT safe**. At the same time, if you don't
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
then `Context.ClientIP()` will return the remote address directly to avoid some
unnecessary computation.
```go
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.SetTrustedProxies([]string{"192.168.1.2"})
router.GET("/", func(c *gin.Context) {
// If the client is 192.168.1.2, use the X-Forwarded-For
// header to deduce the original client IP from the trust-
// worthy parts of that header.
// Otherwise, simply return the direct client IP
fmt.Printf("ClientIP: %s\n", c.ClientIP())
})
router.Run()
}
```
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
to skip TrustedProxies check, it has a higher priority than TrustedProxies.
Look at the example below:
```go
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// Use predefined header gin.PlatformXXX
router.TrustedPlatform = gin.PlatformGoogleAppEngine
// Or set your own trusted request header for another trusted proxy service
// Don't set it to any suspect request header, it's unsafe
router.TrustedPlatform = "X-CDN-IP"
router.GET("/", func(c *gin.Context) {
// If you set TrustedPlatform, ClientIP() will resolve the
// corresponding header and return IP directly
fmt.Printf("ClientIP: %s\n", c.ClientIP())
})
router.Run()
}
```
## Testing ## Testing
@ -2133,10 +2298,16 @@ The `net/http/httptest` package is preferable way for HTTP testing.
```go ```go
package main package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine { func setupRouter() *gin.Engine {
r := gin.Default() r := gin.Default()
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
return r return r
} }
@ -2164,10 +2335,10 @@ func TestPingRoute(t *testing.T) {
router := setupRouter() router := setupRouter()
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil) req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "pong", w.Body.String()) assert.Equal(t, "pong", w.Body.String())
} }
``` ```
@ -2183,3 +2354,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.

10
any.go Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package gin
type any = interface{}

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package gin package gin
import ( import (
"crypto/subtle"
"encoding/base64" "encoding/base64"
"net/http" "net/http"
"strconv" "strconv"
@ -30,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false return "", false
} }
for _, pair := range a { for _, pair := range a {
if pair.value == authValue { if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
return pair.user, true return pair.user, true
} }
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

10
binding/any.go Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package binding
type any = interface{}

View File

@ -1,7 +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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package binding package binding
@ -21,6 +22,7 @@ const (
MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack" MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml" MIMEYAML = "application/x-yaml"
MIMETOML = "application/toml"
) )
// Binding describes the interface which needs to be implemented for binding the // Binding describes the interface which needs to be implemented for binding the
@ -28,42 +30,43 @@ const (
// the form POST. // the form POST.
type Binding interface { type Binding interface {
Name() string Name() string
Bind(*http.Request, interface{}) error Bind(*http.Request, any) error
} }
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, // BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body. // but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface { type BindingBody interface {
Binding Binding
BindBody([]byte, interface{}) error BindBody([]byte, any) error
} }
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params. // but it reads the Params.
type BindingUri interface { type BindingUri interface {
Name() string Name() string
BindUri(map[string][]string, interface{}) error BindUri(map[string][]string, any) error
} }
// StructValidator is the minimal interface which needs to be implemented in // StructValidator is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness // order for it to be used as the validator engine for ensuring the correctness
// of the request. Gin provides a default implementation for this using // of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2. // https://github.com/go-playground/validator/tree/v10.6.1.
type StructValidator interface { type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned. // If the received type is a slice|array, the validation should be performed travel on every element.
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed. // If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned. // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned. // Otherwise nil must be returned.
ValidateStruct(interface{}) error ValidateStruct(any) error
// Engine returns the underlying validator engine which powers the // Engine returns the underlying validator engine which powers the
// StructValidator implementation. // StructValidator implementation.
Engine() interface{} Engine() any
} }
// Validator is the default validator which implements the StructValidator // Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
// under the hood. // under the hood.
var Validator StructValidator = &defaultValidator{} var Validator StructValidator = &defaultValidator{}
@ -81,6 +84,7 @@ var (
YAML = yamlBinding{} YAML = yamlBinding{}
Uri = uriBinding{} Uri = uriBinding{}
Header = headerBinding{} Header = headerBinding{}
TOML = tomlBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method
@ -101,6 +105,8 @@ func Default(method, contentType string) Binding {
return MsgPack return MsgPack
case MIMEYAML: case MIMEYAML:
return YAML return YAML
case MIMETOML:
return TOML
case MIMEMultipartPOSTForm: case MIMEMultipartPOSTForm:
return FormMultipart return FormMultipart
default: // case MIMEPOSTForm: default: // case MIMEPOSTForm:
@ -108,7 +114,7 @@ func Default(method, contentType string) Binding {
} }
} }
func validate(obj interface{}) error { func validate(obj any) error {
if Validator == nil { if Validator == nil {
return nil return nil
} }

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package binding package binding

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build nomsgpack
// +build nomsgpack // +build nomsgpack
package binding package binding
@ -19,6 +20,7 @@ const (
MIMEMultipartPOSTForm = "multipart/form-data" MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf" MIMEPROTOBUF = "application/x-protobuf"
MIMEYAML = "application/x-yaml" MIMEYAML = "application/x-yaml"
MIMETOML = "application/toml"
) )
// Binding describes the interface which needs to be implemented for binding the // Binding describes the interface which needs to be implemented for binding the
@ -26,42 +28,42 @@ const (
// the form POST. // the form POST.
type Binding interface { type Binding interface {
Name() string Name() string
Bind(*http.Request, interface{}) error Bind(*http.Request, any) error
} }
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, // BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body. // but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface { type BindingBody interface {
Binding Binding
BindBody([]byte, interface{}) error BindBody([]byte, any) error
} }
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params. // but it reads the Params.
type BindingUri interface { type BindingUri interface {
Name() string Name() string
BindUri(map[string][]string, interface{}) error BindUri(map[string][]string, any) error
} }
// StructValidator is the minimal interface which needs to be implemented in // StructValidator is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness // order for it to be used as the validator engine for ensuring the correctness
// of the request. Gin provides a default implementation for this using // of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2. // https://github.com/go-playground/validator/tree/v10.6.1.
type StructValidator interface { type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned. // If the received type is not a struct, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed. // If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned. // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned. // Otherwise nil must be returned.
ValidateStruct(interface{}) error ValidateStruct(any) error
// Engine returns the underlying validator engine which powers the // Engine returns the underlying validator engine which powers the
// StructValidator implementation. // StructValidator implementation.
Engine() interface{} Engine() any
} }
// Validator is the default validator which implements the StructValidator // Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
// under the hood. // under the hood.
var Validator StructValidator = &defaultValidator{} var Validator StructValidator = &defaultValidator{}
@ -78,6 +80,7 @@ var (
YAML = yamlBinding{} YAML = yamlBinding{}
Uri = uriBinding{} Uri = uriBinding{}
Header = headerBinding{} Header = headerBinding{}
TOML = tomlBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method
@ -98,12 +101,14 @@ func Default(method, contentType string) Binding {
return YAML return YAML
case MIMEMultipartPOSTForm: case MIMEMultipartPOSTForm:
return FormMultipart return FormMultipart
case MIMETOML:
return TOML
default: // case MIMEPOSTForm: default: // case MIMEPOSTForm:
return Form return Form
} }
} }
func validate(obj interface{}) error { func validate(obj any) error {
if Validator == nil { if Validator == nil {
return nil return nil
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -20,8 +20,8 @@ import (
"time" "time"
"github.com/gin-gonic/gin/testdata/protoexample" "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
) )
type appkey struct { type appkey struct {
@ -35,7 +35,7 @@ type QueryTest struct {
} }
type FooStruct struct { type FooStruct struct {
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
} }
type FooBarStruct struct { type FooBarStruct struct {
@ -61,11 +61,11 @@ type FooDefaultBarStruct struct {
} }
type FooStructUseNumber struct { type FooStructUseNumber struct {
Foo interface{} `json:"foo" binding:"required"` Foo any `json:"foo" binding:"required"`
} }
type FooStructDisallowUnknownFields struct { type FooStructDisallowUnknownFields struct {
Foo interface{} `json:"foo" binding:"required"` Foo any `json:"foo" binding:"required"`
} }
type FooBarStructForTimeType struct { type FooBarStructForTimeType struct {
@ -93,7 +93,7 @@ type FooStructForTimeTypeFailLocation struct {
} }
type FooStructForMapType struct { type FooStructForMapType struct {
MapFoo map[string]interface{} `form:"map_foo"` MapFoo map[string]any `form:"map_foo"`
} }
type FooStructForIgnoreFormTag struct { type FooStructForIgnoreFormTag struct {
@ -106,7 +106,7 @@ type InvalidNameType struct {
type InvalidNameMapType struct { type InvalidNameMapType struct {
TestName struct { TestName struct {
MapFoo map[string]interface{} `form:"map_foo"` MapFoo map[string]any `form:"map_foo"`
} }
} }
@ -128,7 +128,7 @@ type FooStructForStructPointerType struct {
type FooStructForSliceMapType struct { type FooStructForSliceMapType struct {
// Unknown type: not support map // Unknown type: not support map
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` SliceMapFoo []map[string]any `form:"slice_map_foo"`
} }
type FooStructForBoolType struct { type FooStructForBoolType struct {
@ -141,7 +141,7 @@ type FooStructForStringPtrType struct {
} }
type FooStructForMapPtrType struct { type FooStructForMapPtrType struct {
PtrBar *map[string]interface{} `form:"ptr_bar"` PtrBar *map[string]any `form:"ptr_bar"`
} }
func TestBindingDefault(t *testing.T) { func TestBindingDefault(t *testing.T) {
@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) {
assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("POST", MIMEYAML))
assert.Equal(t, YAML, Default("PUT", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML))
assert.Equal(t, TOML, Default("POST", MIMETOML))
assert.Equal(t, TOML, Default("PUT", MIMETOML))
} }
func TestBindingJSONNilBody(t *testing.T) { func TestBindingJSONNilBody(t *testing.T) {
@ -181,6 +184,20 @@ func TestBindingJSON(t *testing.T) {
`{"foo": "bar"}`, `{"bar": "foo"}`) `{"foo": "bar"}`, `{"bar": "foo"}`)
} }
func TestBindingJSONSlice(t *testing.T) {
EnableDecoderDisallowUnknownFields = true
defer func() {
EnableDecoderDisallowUnknownFields = false
}()
testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": ""}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`)
}
func TestBindingJSONUseNumber(t *testing.T) { func TestBindingJSONUseNumber(t *testing.T) {
testBodyBindingUseNumber(t, testBodyBindingUseNumber(t,
JSON, "json", JSON, "json",
@ -440,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) {
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>") "<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
} }
func TestBindingTOML(t *testing.T) {
testBodyBinding(t,
TOML, "toml",
"/", "/",
`foo="bar"`, `bar="foo"`)
}
func TestBindingTOMLFail(t *testing.T) {
testBodyBindingFail(t,
TOML, "toml",
"/", "/",
`foo=\n"bar"`, `bar="foo"`)
}
func TestBindingYAML(t *testing.T) { func TestBindingYAML(t *testing.T) {
testBodyBinding(t, testBodyBinding(t,
YAML, "yaml", YAML, "yaml",
@ -754,7 +785,7 @@ func TestHeaderBinding(t *testing.T) {
req.Header.Add("fail", `{fail:fail}`) req.Header.Add("fail", `{fail:fail}`)
type failStruct struct { type failStruct struct {
Fail map[string]interface{} `header:"fail"` Fail map[string]any `header:"fail"`
} }
err := h.Bind(req, &failStruct{}) err := h.Bind(req, &failStruct{})
@ -775,11 +806,11 @@ func TestUriBinding(t *testing.T) {
assert.Equal(t, "thinkerou", tag.Name) assert.Equal(t, "thinkerou", tag.Name)
type NotSupportStruct struct { type NotSupportStruct struct {
Name map[string]interface{} `uri:"name"` Name map[string]any `uri:"name"`
} }
var not NotSupportStruct var not NotSupportStruct
assert.Error(t, b.BindUri(m, &not)) assert.Error(t, b.BindUri(m, &not))
assert.Equal(t, map[string]interface{}(nil), not.Name) assert.Equal(t, map[string]any(nil), not.Name)
} }
func TestUriInnerBinding(t *testing.T) { func TestUriInnerBinding(t *testing.T) {
@ -818,7 +849,6 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba
assert.Equal(t, 1, obj.Page) assert.Equal(t, 1, obj.Page)
assert.Equal(t, 2, obj.Size) assert.Equal(t, 2, obj.Size)
assert.Equal(t, "test-appkey", obj.Appkey) assert.Equal(t, "test-appkey", obj.Appkey)
} }
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
@ -1181,6 +1211,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name())
var obj1 []FooStruct
req := requestWithBody("POST", path, body)
err := b.Bind(req, &obj1)
assert.NoError(t, err)
var obj2 []FooStruct
req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj2)
assert.Error(t, err)
}
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
obj := make(map[string]string) obj := make(map[string]string)
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
@ -1312,6 +1356,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
invalidobj := FooStruct{}
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
req.Header.Add("Content-Type", MIMEPROTOBUF)
err = b.Bind(req, &invalidobj)
assert.Error(t, err)
assert.Equal(t, err.Error(), "obj is not ProtoMessage")
obj = protoexample.Test{} obj = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", MIMEPROTOBUF)

View File

@ -1,11 +1,13 @@
// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package binding
import ( import (
"fmt"
"reflect" "reflect"
"strings"
"sync" "sync"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@ -16,29 +18,73 @@ type defaultValidator struct {
validate *validator.Validate validate *validator.Validate
} }
type SliceValidationError []error
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
func (err SliceValidationError) Error() string {
n := len(err)
switch n {
case 0:
return ""
default:
var b strings.Builder
if err[0] != nil {
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
}
if n > 1 {
for i := 1; i < n; i++ {
if err[i] != nil {
b.WriteString("\n")
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
}
}
}
return b.String()
}
}
var _ StructValidator = &defaultValidator{} var _ StructValidator = &defaultValidator{}
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
func (v *defaultValidator) ValidateStruct(obj interface{}) error { func (v *defaultValidator) ValidateStruct(obj any) error {
if obj == nil {
return nil
}
value := reflect.ValueOf(obj) value := reflect.ValueOf(obj)
valueType := value.Kind() switch value.Kind() {
if valueType == reflect.Ptr { case reflect.Ptr:
valueType = value.Elem().Kind() return v.ValidateStruct(value.Elem().Interface())
} case reflect.Struct:
if valueType == reflect.Struct { return v.validateStruct(obj)
v.lazyinit() case reflect.Slice, reflect.Array:
if err := v.validate.Struct(obj); err != nil { count := value.Len()
return err validateRet := make(SliceValidationError, 0)
for i := 0; i < count; i++ {
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
validateRet = append(validateRet, err)
}
} }
if len(validateRet) == 0 {
return nil
}
return validateRet
default:
return nil
} }
return nil }
// validateStruct receives struct type
func (v *defaultValidator) validateStruct(obj any) error {
v.lazyinit()
return v.validate.Struct(obj)
} }
// Engine returns the underlying validator engine which powers the default // Engine returns the underlying validator engine which powers the default
// Validator instance. This is useful if you want to register custom validations // Validator instance. This is useful if you want to register custom validations
// or struct level validations. See validator GoDoc for more info - // or struct level validations. See validator GoDoc for more info -
// https://godoc.org/gopkg.in/go-playground/validator.v8 // https://pkg.go.dev/github.com/go-playground/validator/v10
func (v *defaultValidator) Engine() interface{} { func (v *defaultValidator) Engine() any {
v.lazyinit() v.lazyinit()
return v.validate return v.validate
} }

View File

@ -0,0 +1,24 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"errors"
"strconv"
"testing"
)
func BenchmarkSliceValidationError(b *testing.B) {
const size int = 100
for i := 0; i < b.N; i++ {
e := make(SliceValidationError, size)
for j := 0; j < size; j++ {
e[j] = errors.New(strconv.Itoa(j))
}
if len(e.Error()) == 0 {
b.Errorf("error")
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright 2020 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"errors"
"testing"
)
func TestSliceValidationError(t *testing.T) {
tests := []struct {
name string
err SliceValidationError
want string
}{
{"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"},
{"has zero elements", SliceValidationError{}, ""},
{"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"},
{"has two elements",
SliceValidationError{
errors.New("first error"),
errors.New("second error"),
},
"[0]: first error\n[1]: second error",
},
{"has many elements",
SliceValidationError{
errors.New("first error"),
errors.New("second error"),
nil,
nil,
nil,
errors.New("last error"),
},
"[0]: first error\n[1]: second error\n[5]: last error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.err.Error(); got != tt.want {
t.Errorf("SliceValidationError.Error() = %v, want %v", got, tt.want)
}
})
}
}
func TestDefaultValidator(t *testing.T) {
type exampleStruct struct {
A string `binding:"max=8"`
B int `binding:"gt=0"`
}
tests := []struct {
name string
v *defaultValidator
obj any
wantErr bool
}{
{"validate nil obj", &defaultValidator{}, nil, false},
{"validate int obj", &defaultValidator{}, 3, false},
{"validate struct failed-1", &defaultValidator{}, exampleStruct{A: "123456789", B: 1}, true},
{"validate struct failed-2", &defaultValidator{}, exampleStruct{A: "12345678", B: 0}, true},
{"validate struct passed", &defaultValidator{}, exampleStruct{A: "12345678", B: 1}, false},
{"validate *struct failed-1", &defaultValidator{}, &exampleStruct{A: "123456789", B: 1}, true},
{"validate *struct failed-2", &defaultValidator{}, &exampleStruct{A: "12345678", B: 0}, true},
{"validate *struct passed", &defaultValidator{}, &exampleStruct{A: "12345678", B: 1}, false},
{"validate []struct failed-1", &defaultValidator{}, []exampleStruct{{A: "123456789", B: 1}}, true},
{"validate []struct failed-2", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 0}}, true},
{"validate []struct passed", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 1}}, false},
{"validate []*struct failed-1", &defaultValidator{}, []*exampleStruct{{A: "123456789", B: 1}}, true},
{"validate []*struct failed-2", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 0}}, true},
{"validate []*struct passed", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 1}}, false},
{"validate *[]struct failed-1", &defaultValidator{}, &[]exampleStruct{{A: "123456789", B: 1}}, true},
{"validate *[]struct failed-2", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 0}}, true},
{"validate *[]struct passed", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 1}}, false},
{"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
{"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
{"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr {
t.Errorf("defaultValidator.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package binding
import ( import (
"errors"
"net/http" "net/http"
) )
@ -18,14 +19,12 @@ func (formBinding) Name() string {
return "form" return "form"
} }
func (formBinding) Bind(req *http.Request, obj interface{}) error { func (formBinding) Bind(req *http.Request, obj any) error {
if err := req.ParseForm(); err != nil { if err := req.ParseForm(); err != nil {
return err return err
} }
if err := req.ParseMultipartForm(defaultMemory); err != nil { if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
if err != http.ErrNotMultipart { return err
return err
}
} }
if err := mapForm(obj, req.Form); err != nil { if err := mapForm(obj, req.Form); err != nil {
return err return err
@ -37,7 +36,7 @@ func (formPostBinding) Name() string {
return "form-urlencoded" return "form-urlencoded"
} }
func (formPostBinding) Bind(req *http.Request, obj interface{}) error { func (formPostBinding) Bind(req *http.Request, obj any) error {
if err := req.ParseForm(); err != nil { if err := req.ParseForm(); err != nil {
return err return err
} }
@ -51,7 +50,7 @@ func (formMultipartBinding) Name() string {
return "multipart/form-data" return "multipart/form-data"
} }
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { func (formMultipartBinding) Bind(req *http.Request, obj any) error {
if err := req.ParseMultipartForm(defaultMemory); err != nil { if err := req.ParseMultipartForm(defaultMemory); err != nil {
return err return err
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -16,22 +16,34 @@ import (
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
) )
var errUnknownType = errors.New("unknown type") var (
errUnknownType = errors.New("unknown type")
func mapUri(ptr interface{}, m map[string][]string) error { // ErrConvertMapStringSlice can not covert to map[string][]string
ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
// ErrConvertToMapString can not convert to map[string]string
ErrConvertToMapString = errors.New("can not convert to map of strings")
)
func mapURI(ptr any, m map[string][]string) error {
return mapFormByTag(ptr, m, "uri") return mapFormByTag(ptr, m, "uri")
} }
func mapForm(ptr interface{}, form map[string][]string) error { func mapForm(ptr any, form map[string][]string) error {
return mapFormByTag(ptr, form, "form") return mapFormByTag(ptr, form, "form")
} }
func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
return mapFormByTag(ptr, form, tag)
}
var emptyField = reflect.StructField{} var emptyField = reflect.StructField{}
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { func mapFormByTag(ptr any, form map[string][]string, tag string) error {
// Check if ptr is a map // Check if ptr is a map
ptrVal := reflect.ValueOf(ptr) ptrVal := reflect.ValueOf(ptr)
var pointed interface{} var pointed any
if ptrVal.Kind() == reflect.Ptr { if ptrVal.Kind() == reflect.Ptr {
ptrVal = ptrVal.Elem() ptrVal = ptrVal.Elem()
pointed = ptrVal.Interface() pointed = ptrVal.Interface()
@ -49,7 +61,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
// setter tries to set value on a walking by fields of a struct // setter tries to set value on a walking by fields of a struct
type setter interface { type setter interface {
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
} }
type formSource map[string][]string type formSource map[string][]string
@ -57,11 +69,11 @@ type formSource map[string][]string
var _ setter = formSource(nil) var _ setter = formSource(nil)
// TrySet tries to set a value by request's form source (like map[string][]string) // TrySet tries to set a value by request's form source (like map[string][]string)
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
return setByForm(value, field, form, tagValue, opt) return setByForm(value, field, form, tagValue, opt)
} }
func mappingByPtr(ptr interface{}, setter setter, tag string) error { func mappingByPtr(ptr any, setter setter, tag string) error {
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
return err return err
} }
@ -71,7 +83,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
return false, nil return false, nil
} }
var vKind = value.Kind() vKind := value.Kind()
if vKind == reflect.Ptr { if vKind == reflect.Ptr {
var isNew bool var isNew bool
@ -80,14 +92,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
isNew = true isNew = true
vPtr = reflect.New(value.Type().Elem()) vPtr = reflect.New(value.Type().Elem())
} }
isSetted, err := mapping(vPtr.Elem(), field, setter, tag) isSet, err := mapping(vPtr.Elem(), field, setter, tag)
if err != nil { if err != nil {
return false, err return false, err
} }
if isNew && isSetted { if isNew && isSet {
value.Set(vPtr) value.Set(vPtr)
} }
return isSetted, nil return isSet, nil
} }
if vKind != reflect.Struct || !field.Anonymous { if vKind != reflect.Struct || !field.Anonymous {
@ -103,19 +115,19 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
if vKind == reflect.Struct { if vKind == reflect.Struct {
tValue := value.Type() tValue := value.Type()
var isSetted bool var isSet bool
for i := 0; i < value.NumField(); i++ { for i := 0; i < value.NumField(); i++ {
sf := tValue.Field(i) sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue continue
} }
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) ok, err := mapping(value.Field(i), sf, setter, tag)
if err != nil { if err != nil {
return false, err return false, err
} }
isSetted = isSetted || ok isSet = isSet || ok
} }
return isSetted, nil return isSet, nil
} }
return false, nil return false, nil
} }
@ -152,7 +164,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
return setter.TrySet(value, field, tagValue, setOpt) return setter.TrySet(value, field, tagValue, setOpt)
} }
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
vs, ok := form[tagValue] vs, ok := form[tagValue]
if !ok && !opt.isDefaultExists { if !ok && !opt.isDefaultExists {
return false, nil return false, nil
@ -198,7 +210,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
case reflect.Int64: case reflect.Int64:
switch value.Interface().(type) { switch value.Interface().(type) {
case time.Duration: case time.Duration:
return setTimeDuration(val, value, field) return setTimeDuration(val, value)
} }
return setIntField(val, 64, value) return setIntField(val, 64, value)
case reflect.Uint: case reflect.Uint:
@ -298,7 +310,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
t := time.Unix(tv/int64(d), tv%int64(d)) t := time.Unix(tv/int64(d), tv%int64(d))
value.Set(reflect.ValueOf(t)) value.Set(reflect.ValueOf(t))
return nil return nil
} }
if val == "" { if val == "" {
@ -348,7 +359,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
return nil return nil
} }
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { func setTimeDuration(val string, value reflect.Value) error {
d, err := time.ParseDuration(val) d, err := time.ParseDuration(val)
if err != nil { if err != nil {
return err return err
@ -365,13 +376,13 @@ func head(str, sep string) (head string, tail string) {
return str[:idx], str[idx+len(sep):] return str[:idx], str[idx+len(sep):]
} }
func setFormMap(ptr interface{}, form map[string][]string) error { func setFormMap(ptr any, form map[string][]string) error {
el := reflect.TypeOf(ptr).Elem() el := reflect.TypeOf(ptr).Elem()
if el.Kind() == reflect.Slice { if el.Kind() == reflect.Slice {
ptrMap, ok := ptr.(map[string][]string) ptrMap, ok := ptr.(map[string][]string)
if !ok { if !ok {
return errors.New("cannot convert to map slices of strings") return ErrConvertMapStringSlice
} }
for k, v := range form { for k, v := range form {
ptrMap[k] = v ptrMap[k] = v
@ -382,7 +393,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
ptrMap, ok := ptr.(map[string]string) ptrMap, ok := ptr.(map[string]string)
if !ok { if !ok {
return errors.New("cannot convert to map of strings") return ErrConvertToMapString
} }
for k, v := range form { for k, v := range form {
ptrMap[k] = v[len(v)-1] // pick last ptrMap[k] = v[len(v)-1] // pick last

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -18,9 +18,9 @@ func TestMappingBaseTypes(t *testing.T) {
} }
for _, tt := range []struct { for _, tt := range []struct {
name string name string
value interface{} value any
form string form string
expect interface{} expect any
}{ }{
{"base type", struct{ F int }{}, "9", int(9)}, {"base type", struct{ F int }{}, "9", int(9)},
{"base type", struct{ F int8 }{}, "9", int8(9)}, {"base type", struct{ F int8 }{}, "9", int8(9)},
@ -131,7 +131,7 @@ func TestMappingURI(t *testing.T) {
var s struct { var s struct {
F int `uri:"field"` F int `uri:"field"`
} }
err := mapUri(&s, map[string][]string{"field": {"6"}}) err := mapURI(&s, map[string][]string{"field": {"6"}})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int(6), s.F) assert.Equal(t, int(6), s.F)
} }
@ -145,6 +145,15 @@ func TestMappingForm(t *testing.T) {
assert.Equal(t, int(6), s.F) assert.Equal(t, int(6), s.F)
} }
func TestMapFormWithTag(t *testing.T) {
var s struct {
F int `externalTag:"field"`
}
err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag")
assert.NoError(t, err)
assert.Equal(t, int(6), s.F)
}
func TestMappingTime(t *testing.T) { func TestMappingTime(t *testing.T) {
var s struct { var s struct {
Time time.Time Time time.Time

View File

@ -1,3 +1,7 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding package binding
import ( import (
@ -12,7 +16,7 @@ func (headerBinding) Name() string {
return "header" return "header"
} }
func (headerBinding) Bind(req *http.Request, obj interface{}) error { func (headerBinding) Bind(req *http.Request, obj any) error {
if err := mapHeader(obj, req.Header); err != nil { if err := mapHeader(obj, req.Header); err != nil {
return err return err
@ -21,7 +25,7 @@ func (headerBinding) Bind(req *http.Request, obj interface{}) error {
return validate(obj) return validate(obj)
} }
func mapHeader(ptr interface{}, h map[string][]string) error { func mapHeader(ptr any, h map[string][]string) error {
return mappingByPtr(ptr, headerSource(h), "header") return mappingByPtr(ptr, headerSource(h), "header")
} }
@ -29,6 +33,6 @@ type headerSource map[string][]string
var _ setter = headerSource(nil) var _ setter = headerSource(nil)
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,7 +6,7 @@ package binding
import ( import (
"bytes" "bytes"
"fmt" "errors"
"io" "io"
"net/http" "net/http"
@ -30,18 +30,18 @@ func (jsonBinding) Name() string {
return "json" return "json"
} }
func (jsonBinding) Bind(req *http.Request, obj interface{}) error { func (jsonBinding) Bind(req *http.Request, obj any) error {
if req == nil || req.Body == nil { if req == nil || req.Body == nil {
return fmt.Errorf("invalid request") return errors.New("invalid request")
} }
return decodeJSON(req.Body, obj) return decodeJSON(req.Body, obj)
} }
func (jsonBinding) BindBody(body []byte, obj interface{}) error { func (jsonBinding) BindBody(body []byte, obj any) error {
return decodeJSON(bytes.NewReader(body), obj) return decodeJSON(bytes.NewReader(body), obj)
} }
func decodeJSON(r io.Reader, obj interface{}) error { func decodeJSON(r io.Reader, obj any) error {
decoder := json.NewDecoder(r) decoder := json.NewDecoder(r)
if EnableDecoderUseNumber { if EnableDecoderUseNumber {
decoder.UseNumber() decoder.UseNumber()

View File

@ -1,7 +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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package binding package binding
@ -20,15 +21,15 @@ func (msgpackBinding) Name() string {
return "msgpack" return "msgpack"
} }
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { func (msgpackBinding) Bind(req *http.Request, obj any) error {
return decodeMsgPack(req.Body, obj) return decodeMsgPack(req.Body, obj)
} }
func (msgpackBinding) BindBody(body []byte, obj interface{}) error { func (msgpackBinding) BindBody(body []byte, obj any) error {
return decodeMsgPack(bytes.NewReader(body), obj) return decodeMsgPack(bytes.NewReader(body), obj)
} }
func decodeMsgPack(r io.Reader, obj interface{}) error { func decodeMsgPack(r io.Reader, obj any) error {
cdc := new(codec.MsgpackHandle) cdc := new(codec.MsgpackHandle)
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
return err return err

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package binding package binding
@ -25,7 +26,7 @@ func TestMsgpackBindingBindBody(t *testing.T) {
assert.Equal(t, "FOO", s.Foo) assert.Equal(t, "FOO", s.Foo)
} }
func msgpackBody(t *testing.T, obj interface{}) []byte { func msgpackBody(t *testing.T, obj any) []byte {
var bs bytes.Buffer var bs bytes.Buffer
h := &codec.MsgpackHandle{} h := &codec.MsgpackHandle{}
err := codec.NewEncoder(&bs, h).Encode(obj) err := codec.NewEncoder(&bs, h).Encode(obj)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -15,8 +15,16 @@ type multipartRequest http.Request
var _ setter = (*multipartRequest)(nil) var _ setter = (*multipartRequest)(nil)
var (
// ErrMultiFileHeader multipart.FileHeader invalid
ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader")
// ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid
ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader")
)
// TrySet tries to set a value by the multipart request with the binding a form file // TrySet tries to set a value by the multipart request with the binding a form file
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {
if files := r.MultipartForm.File[key]; len(files) != 0 { if files := r.MultipartForm.File[key]; len(files) != 0 {
return setByMultipartFormFile(value, field, files) return setByMultipartFormFile(value, field, files)
} }
@ -24,7 +32,7 @@ func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField
return setByForm(value, field, r.MultipartForm.Value, key, opt) return setByForm(value, field, r.MultipartForm.Value, key, opt)
} }
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
switch value.Kind() { switch value.Kind() {
case reflect.Ptr: case reflect.Ptr:
switch value.Interface().(type) { switch value.Interface().(type) {
@ -40,26 +48,26 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
} }
case reflect.Slice: case reflect.Slice:
slice := reflect.MakeSlice(value.Type(), len(files), len(files)) slice := reflect.MakeSlice(value.Type(), len(files), len(files))
isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
if err != nil || !isSetted { if err != nil || !isSet {
return isSetted, err return isSet, err
} }
value.Set(slice) value.Set(slice)
return true, nil return true, nil
case reflect.Array: case reflect.Array:
return setArrayOfMultipartFormFiles(value, field, files) return setArrayOfMultipartFormFiles(value, field, files)
} }
return false, errors.New("unsupported field type for multipart.FileHeader") return false, ErrMultiFileHeader
} }
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
if value.Len() != len(files) { if value.Len() != len(files) {
return false, errors.New("unsupported len of array for []*multipart.FileHeader") return false, ErrMultiFileHeaderLenInvalid
} }
for i := range files { for i := range files {
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
if err != nil || !setted { if err != nil || !set {
return setted, err return set, err
} }
} }
return true, nil return true, nil

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -76,7 +76,7 @@ func TestFormMultipartBindingBindError(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
name string name string
s interface{} s any
}{ }{
{"wrong type", &struct { {"wrong type", &struct {
Files int `form:"file"` Files int `form:"file"`
@ -124,7 +124,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
assert.Equal(t, file.Filename, fh.Filename) assert.Equal(t, file.Filename, fh.Filename)
// assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 assert.Equal(t, int64(len(file.Content)), fh.Size)
fl, err := fh.Open() fl, err := fh.Open()
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package binding
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/golang/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type protobufBinding struct{} type protobufBinding struct{}
@ -17,7 +18,7 @@ func (protobufBinding) Name() string {
return "protobuf" return "protobuf"
} }
func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { func (b protobufBinding) Bind(req *http.Request, obj any) error {
buf, err := ioutil.ReadAll(req.Body) buf, err := ioutil.ReadAll(req.Body)
if err != nil { if err != nil {
return err return err
@ -25,8 +26,12 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
return b.BindBody(buf, obj) return b.BindBody(buf, obj)
} }
func (protobufBinding) BindBody(body []byte, obj interface{}) error { func (protobufBinding) BindBody(body []byte, obj any) error {
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { msg, ok := obj.(proto.Message)
if !ok {
return errors.New("obj is not ProtoMessage")
}
if err := proto.Unmarshal(body, msg); err != nil {
return err return err
} }
// Here it's same to return validate(obj), but util now we can't add // Here it's same to return validate(obj), but util now we can't add

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,7 +12,7 @@ func (queryBinding) Name() string {
return "query" return "query"
} }
func (queryBinding) Bind(req *http.Request, obj interface{}) error { func (queryBinding) Bind(req *http.Request, obj any) error {
values := req.URL.Query() values := req.URL.Query()
if err := mapForm(obj, values); err != nil { if err := mapForm(obj, values); err != nil {
return err return err

35
binding/toml.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"bytes"
"io"
"net/http"
"github.com/pelletier/go-toml/v2"
)
type tomlBinding struct{}
func (tomlBinding) Name() string {
return "toml"
}
func decodeToml(r io.Reader, obj any) error {
decoder := toml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil {
return err
}
return decoder.Decode(obj)
}
func (tomlBinding) Bind(req *http.Request, obj any) error {
return decodeToml(req.Body, obj)
}
func (tomlBinding) BindBody(body []byte, obj any) error {
return decodeToml(bytes.NewReader(body), obj)
}

22
binding/toml_test.go Normal file
View 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)
}

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -10,8 +10,8 @@ func (uriBinding) Name() string {
return "uri" return "uri"
} }
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error { func (uriBinding) BindUri(m map[string][]string, obj any) error {
if err := mapUri(obj, m); err != nil { if err := mapURI(obj, m); err != nil {
return err return err
} }
return validate(obj) return validate(obj)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -59,7 +59,7 @@ type structNoValidationValues struct {
StructSlice []substructNoValidation StructSlice []substructNoValidation
InterfaceSlice []testInterface InterfaceSlice []testInterface
UniversalInterface interface{} UniversalInterface any
CustomInterface testInterface CustomInterface testInterface
FloatMap map[string]float32 FloatMap map[string]float32
@ -169,7 +169,7 @@ func TestValidateNoValidationPointers(t *testing.T) {
//assert.Equal(t, origin, test) //assert.Equal(t, origin, test)
} }
type Object map[string]interface{} type Object map[string]any
func TestValidatePrimitives(t *testing.T) { func TestValidatePrimitives(t *testing.T) {
obj := Object{"foo": "bar", "bar": 1} obj := Object{"foo": "bar", "bar": 1}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -17,14 +17,14 @@ func (xmlBinding) Name() string {
return "xml" return "xml"
} }
func (xmlBinding) Bind(req *http.Request, obj interface{}) error { func (xmlBinding) Bind(req *http.Request, obj any) error {
return decodeXML(req.Body, obj) return decodeXML(req.Body, obj)
} }
func (xmlBinding) BindBody(body []byte, obj interface{}) error { func (xmlBinding) BindBody(body []byte, obj any) error {
return decodeXML(bytes.NewReader(body), obj) return decodeXML(bytes.NewReader(body), obj)
} }
func decodeXML(r io.Reader, obj interface{}) error { func decodeXML(r io.Reader, obj any) error {
decoder := xml.NewDecoder(r) decoder := xml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -18,15 +18,15 @@ func (yamlBinding) Name() string {
return "yaml" return "yaml"
} }
func (yamlBinding) Bind(req *http.Request, obj interface{}) error { func (yamlBinding) Bind(req *http.Request, obj any) error {
return decodeYAML(req.Body, obj) return decodeYAML(req.Body, obj)
} }
func (yamlBinding) BindBody(body []byte, obj interface{}) error { func (yamlBinding) BindBody(body []byte, obj any) error {
return decodeYAML(bytes.NewReader(body), obj) return decodeYAML(bytes.NewReader(body), obj)
} }
func decodeYAML(r io.Reader, obj interface{}) error { func decodeYAML(r io.Reader, obj any) error {
decoder := yaml.NewDecoder(r) decoder := yaml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,9 +6,9 @@ package gin
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"math" "math"
"mime/multipart" "mime/multipart"
"net" "net"
@ -34,12 +34,17 @@ const (
MIMEPOSTForm = binding.MIMEPOSTForm MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
MIMEYAML = binding.MIMEYAML MIMEYAML = binding.MIMEYAML
MIMETOML = binding.MIMETOML
) )
// BodyBytesKey indicates a default body bytes key. // BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
const abortIndex int8 = math.MaxInt8 / 2 // ContextKey is the key that a Context returns itself for.
const ContextKey = "_gin-gonic/gin/contextkey"
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1
// Context is the most important part of gin. It allows us to pass variables between middleware, // Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example. // manage the flow, validate the JSON of a request and render a JSON response for example.
@ -53,14 +58,15 @@ type Context struct {
index int8 index int8
fullPath string fullPath string
engine *Engine engine *Engine
params *Params params *Params
skippedNodes *[]skippedNode
// This mutex protect Keys map // This mutex protects Keys map.
mu sync.RWMutex mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request. // Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{} Keys map[string]any
// Errors is a list of errors attached to all the handlers/middlewares who used this context. // Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs Errors errorMsgs
@ -68,10 +74,10 @@ type Context struct {
// Accepted defines a list of manually accepted formats for content negotiation. // Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string Accepted []string
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() // queryCache caches the query result from c.Request.URL.Query().
queryCache url.Values queryCache url.Values
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters. // or PUT body parameters.
formCache url.Values formCache url.Values
@ -86,17 +92,19 @@ type Context struct {
func (c *Context) reset() { func (c *Context) reset() {
c.Writer = &c.writermem c.Writer = &c.writermem
c.Params = c.Params[0:0] c.Params = c.Params[:0]
c.handlers = nil c.handlers = nil
c.index = -1 c.index = -1
c.fullPath = "" c.fullPath = ""
c.Keys = nil c.Keys = nil
c.Errors = c.Errors[0:0] c.Errors = c.Errors[:0]
c.Accepted = nil c.Accepted = nil
c.queryCache = nil c.queryCache = nil
c.formCache = nil c.formCache = nil
*c.params = (*c.params)[0:0] c.sameSite = 0
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
} }
// Copy returns a copy of the current context that can be safely used outside the request's scope. // Copy returns a copy of the current context that can be safely used outside the request's scope.
@ -112,7 +120,7 @@ func (c *Context) Copy() *Context {
cp.Writer = &cp.writermem cp.Writer = &cp.writermem
cp.index = abortIndex cp.index = abortIndex
cp.handlers = nil cp.handlers = nil
cp.Keys = map[string]interface{}{} cp.Keys = map[string]any{}
for k, v := range c.Keys { for k, v := range c.Keys {
cp.Keys[k] = v cp.Keys[k] = v
} }
@ -191,7 +199,7 @@ func (c *Context) AbortWithStatus(code int) {
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. // AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
// This method stops the chain, writes the status code and return a JSON body. // This method stops the chain, writes the status code and return a JSON body.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) { func (c *Context) AbortWithStatusJSON(code int, jsonObj any) {
c.Abort() c.Abort()
c.JSON(code, jsonObj) c.JSON(code, jsonObj)
} }
@ -218,7 +226,8 @@ func (c *Context) Error(err error) *Error {
panic("err is nil") panic("err is nil")
} }
parsedError, ok := err.(*Error) var parsedError *Error
ok := errors.As(err, &parsedError)
if !ok { if !ok {
parsedError = &Error{ parsedError = &Error{
Err: err, Err: err,
@ -236,10 +245,10 @@ func (c *Context) Error(err error) *Error {
// Set is used to store a new key/value pair exclusively for this context. // Set is used to store a new key/value pair exclusively for this context.
// It also lazy initializes c.Keys if it was not used previously. // It also lazy initializes c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{}) { func (c *Context) Set(key string, value any) {
c.mu.Lock() c.mu.Lock()
if c.Keys == nil { if c.Keys == nil {
c.Keys = make(map[string]interface{}) c.Keys = make(map[string]any)
} }
c.Keys[key] = value c.Keys[key] = value
@ -247,8 +256,8 @@ func (c *Context) Set(key string, value interface{}) {
} }
// Get returns the value for the given key, ie: (value, true). // Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false) // If the value does not exist it returns (nil, false)
func (c *Context) Get(key string) (value interface{}, exists bool) { func (c *Context) Get(key string) (value any, exists bool) {
c.mu.RLock() c.mu.RLock()
value, exists = c.Keys[key] value, exists = c.Keys[key]
c.mu.RUnlock() c.mu.RUnlock()
@ -256,7 +265,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
} }
// MustGet returns the value for the given key if it exists, otherwise it panics. // MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key string) interface{} { func (c *Context) MustGet(key string) any {
if value, exists := c.Get(key); exists { if value, exists := c.Get(key); exists {
return value return value
} }
@ -344,9 +353,9 @@ func (c *Context) GetStringSlice(key string) (ss []string) {
} }
// GetStringMap returns the value associated with the key as a map of interfaces. // GetStringMap returns the value associated with the key as a map of interfaces.
func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { func (c *Context) GetStringMap(key string) (sm map[string]any) {
if val, ok := c.Get(key); ok && val != nil { if val, ok := c.Get(key); ok && val != nil {
sm, _ = val.(map[string]interface{}) sm, _ = val.(map[string]any)
} }
return return
} }
@ -381,6 +390,15 @@ func (c *Context) Param(key string) string {
return c.Params.ByName(key) return c.Params.ByName(key)
} }
// AddParam adds param to context and
// replaces path param key with given value for e2e testing purposes
// Example Route: "/user/:id"
// AddParam("id", 1)
// Result: "/user/1"
func (c *Context) AddParam(key, value string) {
c.Params = append(c.Params, Param{Key: key, Value: value})
}
// Query returns the keyed url query value if it exists, // Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`. // otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)` // It is shortcut for `c.Request.URL.Query().Get(key)`
@ -389,9 +407,9 @@ func (c *Context) Param(key string) string {
// c.Query("name") == "Manu" // c.Query("name") == "Manu"
// c.Query("value") == "" // c.Query("value") == ""
// c.Query("wtf") == "" // c.Query("wtf") == ""
func (c *Context) Query(key string) string { func (c *Context) Query(key string) (value string) {
value, _ := c.GetQuery(key) value, _ = c.GetQuery(key)
return value return
} }
// DefaultQuery returns the keyed url query value if it exists, // DefaultQuery returns the keyed url query value if it exists,
@ -425,9 +443,9 @@ func (c *Context) GetQuery(key string) (string, bool) {
// QueryArray returns a slice of strings for a given query key. // QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key. // The length of the slice depends on the number of params with the given key.
func (c *Context) QueryArray(key string) []string { func (c *Context) QueryArray(key string) (values []string) {
values, _ := c.GetQueryArray(key) values, _ = c.GetQueryArray(key)
return values return
} }
func (c *Context) initQueryCache() { func (c *Context) initQueryCache() {
@ -442,18 +460,16 @@ func (c *Context) initQueryCache() {
// GetQueryArray returns a slice of strings for a given query key, plus // GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key. // a boolean value whether at least one value exists for the given key.
func (c *Context) GetQueryArray(key string) ([]string, bool) { func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
c.initQueryCache() c.initQueryCache()
if values, ok := c.queryCache[key]; ok && len(values) > 0 { values, ok = c.queryCache[key]
return values, true return
}
return []string{}, false
} }
// QueryMap returns a map for a given query key. // QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) map[string]string { func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ := c.GetQueryMap(key) dicts, _ = c.GetQueryMap(key)
return dicts return
} }
// GetQueryMap returns a map for a given query key, plus a boolean value // GetQueryMap returns a map for a given query key, plus a boolean value
@ -465,9 +481,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
// PostForm returns the specified key from a POST urlencoded form or multipart form // PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`. // when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) string { func (c *Context) PostForm(key string) (value string) {
value, _ := c.GetPostForm(key) value, _ = c.GetPostForm(key)
return value return
} }
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
@ -496,9 +512,9 @@ func (c *Context) GetPostForm(key string) (string, bool) {
// PostFormArray returns a slice of strings for a given form key. // PostFormArray returns a slice of strings for a given form key.
// The length of the slice depends on the number of params with the given key. // The length of the slice depends on the number of params with the given key.
func (c *Context) PostFormArray(key string) []string { func (c *Context) PostFormArray(key string) (values []string) {
values, _ := c.GetPostFormArray(key) values, _ = c.GetPostFormArray(key)
return values return
} }
func (c *Context) initFormCache() { func (c *Context) initFormCache() {
@ -506,7 +522,7 @@ func (c *Context) initFormCache() {
c.formCache = make(url.Values) c.formCache = make(url.Values)
req := c.Request req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if err != http.ErrNotMultipart { if !errors.Is(err, http.ErrNotMultipart) {
debugPrint("error on parse multipart form array: %v", err) debugPrint("error on parse multipart form array: %v", err)
} }
} }
@ -516,18 +532,16 @@ func (c *Context) initFormCache() {
// GetPostFormArray returns a slice of strings for a given form key, plus // GetPostFormArray returns a slice of strings for a given form key, plus
// a boolean value whether at least one value exists for the given key. // a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) ([]string, bool) { func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
c.initFormCache() c.initFormCache()
if values := c.formCache[key]; len(values) > 0 { values, ok = c.formCache[key]
return values, true return
}
return []string{}, false
} }
// PostFormMap returns a map for a given form key. // PostFormMap returns a map for a given form key.
func (c *Context) PostFormMap(key string) map[string]string { func (c *Context) PostFormMap(key string) (dicts map[string]string) {
dicts, _ := c.GetPostFormMap(key) dicts, _ = c.GetPostFormMap(key)
return dicts return
} }
// GetPostFormMap returns a map for a given form key, plus a boolean value // GetPostFormMap returns a map for a given form key, plus a boolean value
@ -591,47 +605,51 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
return err return err
} }
// Bind checks the Content-Type to select a binding engine automatically, // Bind checks the Method and Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used: // Depending on the "Content-Type" header different bindings are used, for example:
// "application/json" --> JSON binding // "application/json" --> JSON binding
// "application/xml" --> XML binding // "application/xml" --> XML binding
// otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer. // It decodes the json payload into the struct specified as a pointer.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error { func (c *Context) Bind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType()) b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b) return c.MustBindWith(obj, b)
} }
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj interface{}) error { func (c *Context) BindJSON(obj any) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON)
} }
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj interface{}) error { func (c *Context) BindXML(obj any) error {
return c.MustBindWith(obj, binding.XML) return c.MustBindWith(obj, binding.XML)
} }
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj interface{}) error { func (c *Context) BindQuery(obj any) error {
return c.MustBindWith(obj, binding.Query) return c.MustBindWith(obj, binding.Query)
} }
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) BindYAML(obj interface{}) error { func (c *Context) BindYAML(obj any) error {
return c.MustBindWith(obj, binding.YAML) return c.MustBindWith(obj, binding.YAML)
} }
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
func (c *Context) BindTOML(obj interface{}) error {
return c.MustBindWith(obj, binding.TOML)
}
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj interface{}) error { func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header) return c.MustBindWith(obj, binding.Header)
} }
// BindUri binds the passed struct pointer using binding.Uri. // BindUri binds the passed struct pointer using binding.Uri.
// It will abort the request with HTTP 400 if any error occurs. // It will abort the request with HTTP 400 if any error occurs.
func (c *Context) BindUri(obj interface{}) error { func (c *Context) BindUri(obj any) error {
if err := c.ShouldBindUri(obj); err != nil { if err := c.ShouldBindUri(obj); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err return err
@ -642,7 +660,7 @@ func (c *Context) BindUri(obj interface{}) error {
// MustBindWith binds the passed struct pointer using the specified binding engine. // MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs. // It will abort the request with HTTP 400 if any error occurs.
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { func (c *Context) MustBindWith(obj any, b binding.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil { if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err return err
@ -650,46 +668,50 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
return nil return nil
} }
// ShouldBind checks the Content-Type to select a binding engine automatically, // ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used: // Depending on the "Content-Type" header different bindings are used, for example:
// "application/json" --> JSON binding // "application/json" --> JSON binding
// "application/xml" --> XML binding // "application/xml" --> XML binding
// otherwise --> returns an error
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer. // It decodes the json payload into the struct specified as a pointer.
// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. // Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (c *Context) ShouldBind(obj interface{}) error { func (c *Context) ShouldBind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType()) b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b) return c.ShouldBindWith(obj, b)
} }
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj interface{}) error { func (c *Context) ShouldBindJSON(obj any) error {
return c.ShouldBindWith(obj, binding.JSON) return c.ShouldBindWith(obj, binding.JSON)
} }
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj interface{}) error { func (c *Context) ShouldBindXML(obj any) error {
return c.ShouldBindWith(obj, binding.XML) return c.ShouldBindWith(obj, binding.XML)
} }
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj interface{}) error { func (c *Context) ShouldBindQuery(obj any) error {
return c.ShouldBindWith(obj, binding.Query) return c.ShouldBindWith(obj, binding.Query)
} }
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) ShouldBindYAML(obj interface{}) error { func (c *Context) ShouldBindYAML(obj any) error {
return c.ShouldBindWith(obj, binding.YAML) return c.ShouldBindWith(obj, binding.YAML)
} }
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
func (c *Context) ShouldBindTOML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.TOML)
}
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj interface{}) error { func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header) return c.ShouldBindWith(obj, binding.Header)
} }
// ShouldBindUri binds the passed struct pointer using the specified binding engine. // ShouldBindUri binds the passed struct pointer using the specified binding engine.
func (c *Context) ShouldBindUri(obj interface{}) error { func (c *Context) ShouldBindUri(obj any) error {
m := make(map[string][]string) m := make(map[string][]string)
for _, v := range c.Params { for _, v := range c.Params {
m[v.Key] = []string{v.Value} m[v.Key] = []string{v.Value}
@ -699,7 +721,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error {
// ShouldBindWith binds the passed struct pointer using the specified binding engine. // ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package. // See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
return b.Bind(c.Request, obj) return b.Bind(c.Request, obj)
} }
@ -708,7 +730,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
// //
// NOTE: This method reads the body before binding. So you should use // NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once. // ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) { func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
var body []byte var body []byte
if cb, ok := c.Get(BodyBytesKey); ok { if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok { if cbb, ok := cb.([]byte); ok {
@ -725,32 +747,55 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
return bb.BindBody(body, obj) return bb.BindBody(body, obj)
} }
// ClientIP implements a best effort algorithm to return the real client IP, it parses // ClientIP implements one best effort algorithm to return the real client IP.
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
// the remote IP (coming from Request.RemoteAddr) is returned.
func (c *Context) ClientIP() string { func (c *Context) ClientIP() string {
if c.engine.ForwardedByClientIP { // Check if we're running on a trusted platform, continue running backwards if error
clientIP := c.requestHeader("X-Forwarded-For") if c.engine.TrustedPlatform != "" {
clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) // Developers can define their own header of Trusted Platform or use predefined constants
if clientIP == "" { if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" {
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) return addr
}
if clientIP != "" {
return clientIP
} }
} }
// Legacy "AppEngine" flag
if c.engine.AppEngine { if c.engine.AppEngine {
log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`)
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
return addr return addr
} }
} }
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil { // It also checks if the remoteIP is a trusted proxy or not.
return ip // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined by Engine.SetTrustedProxies()
remoteIP := net.ParseIP(c.RemoteIP())
if remoteIP == nil {
return ""
} }
trusted := c.engine.isTrustedProxy(remoteIP)
return "" if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
for _, headerName := range c.engine.RemoteIPHeaders {
ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
if valid {
return ip
}
}
}
return remoteIP.String()
}
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
func (c *Context) RemoteIP() string {
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err != nil {
return ""
}
return ip
} }
// ContentType returns the Content-Type header of the request. // ContentType returns the Content-Type header of the request.
@ -794,7 +839,7 @@ func (c *Context) Status(code int) {
c.Writer.WriteHeader(code) c.Writer.WriteHeader(code)
} }
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value). // Header is an intelligent shortcut for c.Writer.Header().Set(key, value).
// It writes a header in the response. // It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)` // If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) { func (c *Context) Header(key, value string) {
@ -810,7 +855,7 @@ func (c *Context) GetHeader(key string) string {
return c.requestHeader(key) return c.requestHeader(key)
} }
// GetRawData return stream data. // GetRawData returns stream data.
func (c *Context) GetRawData() ([]byte, error) { func (c *Context) GetRawData() ([]byte, error) {
return ioutil.ReadAll(c.Request.Body) return ioutil.ReadAll(c.Request.Body)
} }
@ -870,30 +915,30 @@ func (c *Context) Render(code int, r render.Render) {
// HTML renders the HTTP template specified by its file name. // HTML renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html". // It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/ // See http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, obj interface{}) { func (c *Context) HTML(code int, name string, obj any) {
instance := c.engine.HTMLRender.Instance(name, obj) instance := c.engine.HTMLRender.Instance(name, obj)
c.Render(code, instance) c.Render(code, instance)
} }
// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
// WARNING: we recommend to use this only for development purposes since printing pretty JSON is // WARNING: we recommend using this only for development purposes since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead. // more CPU and bandwidth consuming. Use Context.JSON() instead.
func (c *Context) IndentedJSON(code int, obj interface{}) { func (c *Context) IndentedJSON(code int, obj any) {
c.Render(code, render.IndentedJSON{Data: obj}) c.Render(code, render.IndentedJSON{Data: obj})
} }
// SecureJSON serializes the given struct as Secure JSON into the response body. // SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values. // Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) SecureJSON(code int, obj interface{}) { func (c *Context) SecureJSON(code int, obj any) {
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj}) c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
} }
// JSONP serializes the given struct as JSON into the response body. // JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client. // It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript". // It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj interface{}) { func (c *Context) JSONP(code int, obj any) {
callback := c.DefaultQuery("callback", "") callback := c.DefaultQuery("callback", "")
if callback == "" { if callback == "" {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON{Data: obj})
@ -904,40 +949,45 @@ func (c *Context) JSONP(code int, obj interface{}) {
// JSON serializes the given struct as JSON into the response body. // JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) { func (c *Context) JSON(code int, obj any) {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON{Data: obj})
} }
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) AsciiJSON(code int, obj interface{}) { func (c *Context) AsciiJSON(code int, obj any) {
c.Render(code, render.AsciiJSON{Data: obj}) c.Render(code, render.AsciiJSON{Data: obj})
} }
// PureJSON serializes the given struct as JSON into the response body. // PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. // PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) { func (c *Context) PureJSON(code int, obj any) {
c.Render(code, render.PureJSON{Data: obj}) c.Render(code, render.PureJSON{Data: obj})
} }
// XML serializes the given struct as XML into the response body. // XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml". // It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) { func (c *Context) XML(code int, obj any) {
c.Render(code, render.XML{Data: obj}) c.Render(code, render.XML{Data: obj})
} }
// YAML serializes the given struct as YAML into the response body. // YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) { func (c *Context) YAML(code int, obj any) {
c.Render(code, render.YAML{Data: obj}) c.Render(code, render.YAML{Data: obj})
} }
// TOML serializes the given struct as TOML into the response body.
func (c *Context) TOML(code int, obj interface{}) {
c.Render(code, render.TOML{Data: obj})
}
// ProtoBuf serializes the given struct as ProtoBuf into the response body. // ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj interface{}) { func (c *Context) ProtoBuf(code int, obj any) {
c.Render(code, render.ProtoBuf{Data: obj}) c.Render(code, render.ProtoBuf{Data: obj})
} }
// String writes the given string into the response body. // String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...interface{}) { func (c *Context) String(code int, format string, values ...any) {
c.Render(code, render.String{Format: format, Data: values}) c.Render(code, render.String{Format: format, Data: values})
} }
@ -946,7 +996,7 @@ func (c *Context) StringHTML(code int, content string, values ...interface{}) {
c.Render(code, render.StringHTML{Format: content, Data: values}) c.Render(code, render.StringHTML{Format: content, Data: values})
} }
// Redirect returns a HTTP redirect to the specific location. // Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) { func (c *Context) Redirect(code int, location string) {
c.Render(-1, render.Redirect{ c.Render(-1, render.Redirect{
Code: code, Code: code,
@ -992,12 +1042,16 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
// FileAttachment writes the specified file into the body stream in an efficient way // FileAttachment writes the specified file into the body stream in an efficient way
// On the client side, the file will typically be downloaded with the given filename // On the client side, the file will typically be downloaded with the given filename
func (c *Context) FileAttachment(filepath, filename string) { func (c *Context) FileAttachment(filepath, filename string) {
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) if isASCII(filename) {
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
} else {
c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
}
http.ServeFile(c.Writer, c.Request, filepath) http.ServeFile(c.Writer, c.Request, filepath)
} }
// SSEvent writes a Server-Sent Event into the body stream. // SSEvent writes a Server-Sent Event into the body stream.
func (c *Context) SSEvent(name string, message interface{}) { func (c *Context) SSEvent(name string, message any) {
c.Render(-1, sse.Event{ c.Render(-1, sse.Event{
Event: name, Event: name,
Data: message, Data: message,
@ -1031,14 +1085,15 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool {
type Negotiate struct { type Negotiate struct {
Offered []string Offered []string
HTMLName string HTMLName string
HTMLData interface{} HTMLData any
JSONData interface{} JSONData any
XMLData interface{} XMLData any
YAMLData interface{} YAMLData any
Data interface{} Data any
TOMLData any
} }
// Negotiate calls different Render according acceptable Accept format. // Negotiate calls different Render according to acceptable Accept format.
func (c *Context) Negotiate(code int, config Negotiate) { func (c *Context) Negotiate(code int, config Negotiate) {
switch c.NegotiateFormat(config.Offered...) { switch c.NegotiateFormat(config.Offered...) {
case binding.MIMEJSON: case binding.MIMEJSON:
@ -1057,6 +1112,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
data := chooseData(config.YAMLData, config.Data) data := chooseData(config.YAMLData, config.Data)
c.YAML(code, data) c.YAML(code, data)
case binding.MIMETOML:
data := chooseData(config.TOMLData, config.Data)
c.TOML(code, data)
default: default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
} }
@ -1102,34 +1161,47 @@ func (c *Context) SetAccepted(formats ...string) {
/***** GOLANG.ORG/X/NET/CONTEXT *****/ /***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/ /************************************/
// Deadline always returns that there is no deadline (ok==false), // Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
// maybe you want to use Request.Context().Deadline() instead.
func (c *Context) Deadline() (deadline time.Time, ok bool) { func (c *Context) Deadline() (deadline time.Time, ok bool) {
return if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return
}
return c.Request.Context().Deadline()
} }
// Done always returns nil (chan which will wait forever), // Done returns nil (chan which will wait forever) when c.Request has no Context.
// if you want to abort your work when the connection was closed
// you should use Request.Context().Done() instead.
func (c *Context) Done() <-chan struct{} { func (c *Context) Done() <-chan struct{} {
return nil if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Done()
} }
// Err always returns nil, maybe you want to use Request.Context().Err() instead. // Err returns nil when c.Request has no Context.
func (c *Context) Err() error { func (c *Context) Err() error {
return nil if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Err()
} }
// Value returns the value associated with this context for key, or nil // Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with // if no value is associated with key. Successive calls to Value with
// the same key returns the same result. // the same key returns the same result.
func (c *Context) Value(key interface{}) interface{} { func (c *Context) Value(key any) any {
if key == 0 { if key == 0 {
return c.Request return c.Request
} }
if keyAsString, ok := key.(string); ok { if key == ContextKey {
val, _ := c.Get(keyAsString) return c
return val
} }
return nil if keyAsString, ok := key.(string); ok {
if val, exists := c.Get(keyAsString); exists {
return val
}
}
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Value(key)
} }

31
context_1.16_test.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2021 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build !go1.17
// +build !go1.17
package gin
import (
"bytes"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContextFormFileFailed16(t *testing.T) {
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
c.engine.MaxMultipartMemory = 8 << 20
f, err := c.FormFile("file")
assert.Error(t, err)
assert.Nil(t, f)
}

72
context_1.17_test.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2021 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build go1.17
// +build go1.17
package gin
import (
"bytes"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
type interceptedWriter struct {
ResponseWriter
b *bytes.Buffer
}
func (i interceptedWriter) WriteHeader(code int) {
i.Header().Del("X-Test")
i.ResponseWriter.WriteHeader(code)
}
func TestContextFormFileFailed17(t *testing.T) {
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
c.engine.MaxMultipartMemory = 8 << 20
assert.Panics(t, func() {
f, err := c.FormFile("file")
assert.Error(t, err)
assert.Nil(t, f)
})
}
func TestInterceptedHeader(t *testing.T) {
w := httptest.NewRecorder()
c, r := CreateTestContext(w)
r.Use(func(c *Context) {
i := interceptedWriter{
ResponseWriter: c.Writer,
b: bytes.NewBuffer(nil),
}
c.Writer = i
c.Next()
c.Header("X-Test", "overridden")
c.Writer = i.ResponseWriter
})
r.GET("/", func(c *Context) {
c.Header("X-Test", "original")
c.Header("X-Test-2", "present")
c.String(http.StatusOK, "hello world")
})
c.Request = httptest.NewRequest("GET", "/", nil)
r.HandleContext(c)
// Result() has headers frozen when WriteHeaderNow() has been called
// Compared to this time, this is when the response headers will be flushed
// As response is flushed on c.String, the Header cannot be set by the first
// middleware. Assert this
assert.Equal(t, "", w.Result().Header.Get("X-Test"))
assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
}

View File

@ -1,11 +1,12 @@
// +build appengine // Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build appengine
// +build appengine
package gin package gin
func init() { func init() {
defaultAppEngine = true defaultPlatform = PlatformGoogleAppEngine
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,8 +12,10 @@ import (
"html/template" "html/template"
"io" "io"
"mime/multipart" "mime/multipart"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@ -23,10 +25,9 @@ import (
"github.com/gin-contrib/sse" "github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
testdata "github.com/gin-gonic/gin/testdata/protoexample" testdata "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
) )
var _ context.Context = &Context{} var _ context.Context = &Context{}
@ -87,19 +88,6 @@ func TestContextFormFile(t *testing.T) {
assert.NoError(t, c.SaveUploadedFile(f, "test")) assert.NoError(t, c.SaveUploadedFile(f, "test"))
} }
func TestContextFormFileFailed(t *testing.T) {
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
c.engine.MaxMultipartMemory = 8 << 20
f, err := c.FormFile("file")
assert.Error(t, err)
assert.Nil(t, f)
}
func TestContextMultipartForm(t *testing.T) { func TestContextMultipartForm(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
@ -224,7 +212,7 @@ func TestContextSetGetValues(t *testing.T) {
c.Set("uint64", uint64(42)) c.Set("uint64", uint64(42))
c.Set("float32", float32(4.2)) c.Set("float32", float32(4.2))
c.Set("float64", 4.2) c.Set("float64", 4.2)
var a interface{} = 1 var a any = 1
c.Set("intInterface", a) c.Set("intInterface", a)
assert.Exactly(t, c.MustGet("string").(string), "this is a string") assert.Exactly(t, c.MustGet("string").(string), "this is a string")
@ -234,7 +222,6 @@ func TestContextSetGetValues(t *testing.T) {
assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2)) assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2))
assert.Exactly(t, c.MustGet("float64").(float64), 4.2) assert.Exactly(t, c.MustGet("float64").(float64), 4.2)
assert.Exactly(t, c.MustGet("intInterface").(int), 1) assert.Exactly(t, c.MustGet("intInterface").(int), 1)
} }
func TestContextGetString(t *testing.T) { func TestContextGetString(t *testing.T) {
@ -300,7 +287,7 @@ func TestContextGetStringSlice(t *testing.T) {
func TestContextGetStringMap(t *testing.T) { func TestContextGetStringMap(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
var m = make(map[string]interface{}) m := make(map[string]any)
m["foo"] = 1 m["foo"] = 1
c.Set("map", m) c.Set("map", m)
@ -310,7 +297,7 @@ func TestContextGetStringMap(t *testing.T) {
func TestContextGetStringMapString(t *testing.T) { func TestContextGetStringMapString(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
var m = make(map[string]string) m := make(map[string]string)
m["foo"] = "bar" m["foo"] = "bar"
c.Set("map", m) c.Set("map", m)
@ -320,7 +307,7 @@ func TestContextGetStringMapString(t *testing.T) {
func TestContextGetStringMapStringSlice(t *testing.T) { func TestContextGetStringMapStringSlice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
var m = make(map[string][]string) m := make(map[string][]string)
m["foo"] = []string{"foo"} m["foo"] = []string{"foo"}
c.Set("map", m) c.Set("map", m)
@ -369,15 +356,12 @@ func TestContextHandlerNames(t *testing.T) {
} }
func handlerNameTest(c *Context) { func handlerNameTest(c *Context) {
} }
func handlerNameTest2(c *Context) { func handlerNameTest2(c *Context) {
} }
var handlerTest HandlerFunc = func(c *Context) { var handlerTest HandlerFunc = func(c *Context) {
} }
func TestContextHandler(t *testing.T) { func TestContextHandler(t *testing.T) {
@ -659,8 +643,7 @@ func TestContextBodyAllowedForStatus(t *testing.T) {
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
} }
type TestPanicRender struct { type TestPanicRender struct{}
}
func (*TestPanicRender) Render(http.ResponseWriter) error { func (*TestPanicRender) Render(http.ResponseWriter) error {
return errors.New("TestPanicRender") return errors.New("TestPanicRender")
@ -1031,7 +1014,9 @@ func TestContextRenderFile(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) // Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
// else, Content-Type='text/x-go; charset=utf-8'
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
} }
func TestContextRenderFileFromFS(t *testing.T) { func TestContextRenderFileFromFS(t *testing.T) {
@ -1043,7 +1028,9 @@ func TestContextRenderFileFromFS(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) // Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
// else, Content-Type='text/x-go; charset=utf-8'
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
assert.Equal(t, "/some/path", c.Request.URL.Path) assert.Equal(t, "/some/path", c.Request.URL.Path)
} }
@ -1057,7 +1044,20 @@ func TestContextRenderAttachment(t *testing.T) {
assert.Equal(t, 200, w.Code) assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition"))
}
func TestContextRenderUTF8Attachment(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
newFilename := "new🧡_filename.go"
c.Request, _ = http.NewRequest("GET", "/", nil)
c.FileAttachment("./gin.go", newFilename)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
} }
// TestContextRenderYAML tests that the response is serialized as YAML // TestContextRenderYAML tests that the response is serialized as YAML
@ -1073,6 +1073,19 @@ func TestContextRenderYAML(t *testing.T) {
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
} }
// TestContextRenderTOML tests that the response is serialized as TOML
// and Content-Type is set to application/toml
func TestContextRenderTOML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.TOML(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "foo = 'bar'\n", w.Body.String())
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
// and Content-Type is set to application/x-protobuf // and Content-Type is set to application/x-protobuf
// and we just use the example protobuf to check if the response is correct // and we just use the example protobuf to check if the response is correct
@ -1193,6 +1206,36 @@ func TestContextNegotiationWithXML(t *testing.T) {
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
} }
func TestContextNegotiationWithYAML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML},
Data: H{"foo": "bar"},
})
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "foo: bar\n", w.Body.String())
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationWithTOML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML},
Data: H{"foo": "bar"},
})
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "foo = 'bar'\n", w.Body.String())
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationWithHTML(t *testing.T) { func TestContextNegotiationWithHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
@ -1338,7 +1381,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
_, err := buf.ReadFrom(w.Body) _, err := buf.ReadFrom(w.Body)
assert.NoError(t, err) assert.NoError(t, err)
jsonStringBody := buf.String() jsonStringBody := buf.String()
assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody) assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody)
} }
func TestContextError(t *testing.T) { func TestContextError(t *testing.T) {
@ -1404,12 +1447,11 @@ func TestContextAbortWithError(t *testing.T) {
func TestContextClientIP(t *testing.T) { func TestContextClientIP(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()
resetContextForClientIPTests(c)
c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") // Legacy tests (validating that the defaults don't break the
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") // (insecure!) old behaviour)
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
c.Request.RemoteAddr = " 40.40.40.40:42123 "
assert.Equal(t, "20.20.20.20", c.ClientIP()) assert.Equal(t, "20.20.20.20", c.ClientIP())
c.Request.Header.Del("X-Forwarded-For") c.Request.Header.Del("X-Forwarded-For")
@ -1420,7 +1462,7 @@ func TestContextClientIP(t *testing.T) {
c.Request.Header.Del("X-Forwarded-For") c.Request.Header.Del("X-Forwarded-For")
c.Request.Header.Del("X-Real-IP") c.Request.Header.Del("X-Real-IP")
c.engine.AppEngine = true c.engine.TrustedPlatform = PlatformGoogleAppEngine
assert.Equal(t, "50.50.50.50", c.ClientIP()) assert.Equal(t, "50.50.50.50", c.ClientIP())
c.Request.Header.Del("X-Appengine-Remote-Addr") c.Request.Header.Del("X-Appengine-Remote-Addr")
@ -1429,6 +1471,113 @@ func TestContextClientIP(t *testing.T) {
// no port // no port
c.Request.RemoteAddr = "50.50.50.50" c.Request.RemoteAddr = "50.50.50.50"
assert.Empty(t, c.ClientIP()) assert.Empty(t, c.ClientIP())
// Tests exercising the TrustedProxies functionality
resetContextForClientIPTests(c)
// IPv6 support
c.Request.RemoteAddr = "[::1]:12345"
assert.Equal(t, "20.20.20.20", c.ClientIP())
resetContextForClientIPTests(c)
// No trusted proxies
_ = c.engine.SetTrustedProxies([]string{})
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Disabled TrustedProxies feature
_ = c.engine.SetTrustedProxies(nil)
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Last proxy is trusted, but the RemoteAddr is not
_ = c.engine.SetTrustedProxies([]string{"30.30.30.30"})
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Only trust RemoteAddr
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
assert.Equal(t, "30.30.30.30", c.ClientIP())
// All steps are trusted
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"})
assert.Equal(t, "20.20.20.20", c.ClientIP())
// Use CIDR
_ = c.engine.SetTrustedProxies([]string{"40.40.25.25/16", "30.30.30.30"})
assert.Equal(t, "20.20.20.20", c.ClientIP())
// Use hostname that resolves to all the proxies
_ = c.engine.SetTrustedProxies([]string{"foo"})
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Use hostname that returns an error
_ = c.engine.SetTrustedProxies([]string{"bar"})
assert.Equal(t, "40.40.40.40", c.ClientIP())
// X-Forwarded-For has a non-IP element
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
c.Request.Header.Set("X-Forwarded-For", " blah ")
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Result from LookupHost has non-IP element. This should never
// happen, but we should test it to make sure we handle it
// gracefully.
_ = c.engine.SetTrustedProxies([]string{"baz"})
c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ")
assert.Equal(t, "40.40.40.40", c.ClientIP())
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
c.Request.Header.Del("X-Forwarded-For")
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"}
assert.Equal(t, "10.10.10.10", c.ClientIP())
c.engine.RemoteIPHeaders = []string{}
c.engine.TrustedPlatform = PlatformGoogleAppEngine
assert.Equal(t, "50.50.50.50", c.ClientIP())
// Use custom TrustedPlatform header
c.engine.TrustedPlatform = "X-CDN-IP"
c.Request.Header.Set("X-CDN-IP", "80.80.80.80")
assert.Equal(t, "80.80.80.80", c.ClientIP())
// wrong header
c.engine.TrustedPlatform = "X-Wrong-Header"
assert.Equal(t, "40.40.40.40", c.ClientIP())
c.Request.Header.Del("X-CDN-IP")
// TrustedPlatform is empty
c.engine.TrustedPlatform = ""
assert.Equal(t, "40.40.40.40", c.ClientIP())
// Test the legacy flag
c.engine.AppEngine = true
assert.Equal(t, "50.50.50.50", c.ClientIP())
c.engine.AppEngine = false
c.engine.TrustedPlatform = PlatformGoogleAppEngine
c.Request.Header.Del("X-Appengine-Remote-Addr")
assert.Equal(t, "40.40.40.40", c.ClientIP())
c.engine.TrustedPlatform = PlatformCloudflare
assert.Equal(t, "60.60.60.60", c.ClientIP())
c.Request.Header.Del("CF-Connecting-IP")
assert.Equal(t, "40.40.40.40", c.ClientIP())
c.engine.TrustedPlatform = ""
// no port
c.Request.RemoteAddr = "50.50.50.50"
assert.Empty(t, c.ClientIP())
}
func resetContextForClientIPTests(c *Context) {
c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ")
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
c.Request.RemoteAddr = " 40.40.40.40:42123 "
c.engine.TrustedPlatform = ""
c.engine.trustedCIDRs = defaultTrustedCIDRs
c.engine.AppEngine = false
} }
func TestContextContentType(t *testing.T) { func TestContextContentType(t *testing.T) {
@ -1470,6 +1619,7 @@ func TestContextBindWithJSON(t *testing.T) {
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextBindWithXML(t *testing.T) { func TestContextBindWithXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1546,6 +1696,23 @@ func TestContextBindWithYAML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextBindWithTOML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'"))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
var obj struct {
Foo string `toml:"foo"`
Bar string `toml:"bar"`
}
assert.NoError(t, c.BindTOML(&obj))
assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len())
}
func TestContextBadAutoBind(t *testing.T) { func TestContextBadAutoBind(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1679,6 +1846,23 @@ func TestContextShouldBindWithYAML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextShouldBindWithTOML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'"))
c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type
var obj struct {
Foo string `toml:"foo"`
Bar string `toml:"bar"`
}
assert.NoError(t, c.ShouldBindTOML(&obj))
assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len())
}
func TestContextBadAutoShouldBind(t *testing.T) { func TestContextBadAutoShouldBind(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1786,6 +1970,7 @@ func TestContextGolangContext(t *testing.T) {
assert.Equal(t, ti, time.Time{}) assert.Equal(t, ti, time.Time{})
assert.False(t, ok) assert.False(t, ok)
assert.Equal(t, c.Value(0), c.Request) assert.Equal(t, c.Value(0), c.Request)
assert.Equal(t, c.Value(ContextKey), c)
assert.Nil(t, c.Value("foo")) assert.Nil(t, c.Value("foo"))
c.Set("foo", "bar") c.Set("foo", "bar")
@ -1956,8 +2141,8 @@ func TestRaceParamsContextCopy(t *testing.T) {
}(c.Copy(), c.Param("name")) }(c.Copy(), c.Param("name"))
}) })
} }
performRequest(router, "GET", "/name1/api") PerformRequest(router, "GET", "/name1/api")
performRequest(router, "GET", "/name2/api") PerformRequest(router, "GET", "/name2/api")
wg.Wait() wg.Wait()
} }
@ -1973,3 +2158,212 @@ func TestContextWithKeysMutex(t *testing.T) {
assert.Nil(t, value) assert.Nil(t, value)
assert.False(t, err) assert.False(t, err)
} }
func TestRemoteIPFail(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.RemoteAddr = "[:::]:80"
ip := net.ParseIP(c.RemoteIP())
trust := c.engine.isTrustedProxy(ip)
assert.Nil(t, ip)
assert.False(t, trust)
}
func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
deadline, ok := c.Deadline()
assert.Zero(t, deadline)
assert.False(t, ok)
c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
d := time.Now().Add(time.Second)
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
c2.Request = c2.Request.WithContext(ctx)
deadline, ok = c2.Deadline()
assert.Equal(t, d, deadline)
assert.True(t, ok)
}
func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
assert.Nil(t, c.Done())
c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
ctx, cancel := context.WithCancel(context.Background())
c2.Request = c2.Request.WithContext(ctx)
cancel()
assert.NotNil(t, <-c2.Done())
}
func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
assert.Nil(t, c.Err())
c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
ctx, cancel := context.WithCancel(context.Background())
c2.Request = c2.Request.WithContext(ctx)
cancel()
assert.EqualError(t, c2.Err(), context.Canceled.Error())
}
func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
type contextKey string
tests := []struct {
name string
getContextAndKey func() (*Context, any)
value any
}{
{
name: "c with struct context key",
getContextAndKey: func() (*Context, any) {
var key struct{}
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value"))
return c, key
},
value: "value",
},
{
name: "c with string context key",
getContextAndKey: func() (*Context, any) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
return c, contextKey("key")
},
value: "value",
},
{
name: "c with nil http.Request",
getContextAndKey: func() (*Context, any) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request = nil
return c, "key"
},
value: nil,
},
{
name: "c with nil http.Request.Context()",
getContextAndKey: func() (*Context, any) {
c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil)
return c, "key"
},
value: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, key := tt.getContextAndKey()
assert.Equal(t, tt.value, c.Value(key))
})
}
}
func TestContextCopyShouldNotCancel(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
ensureRequestIsOver := make(chan struct{})
wg := &sync.WaitGroup{}
r := New()
r.GET("/", func(ginctx *Context) {
wg.Add(1)
ginctx = ginctx.Copy()
// start async goroutine for calling srv
go func() {
defer wg.Done()
<-ensureRequestIsOver // ensure request is done
req, err := http.NewRequestWithContext(ginctx, http.MethodGet, srv.URL, nil)
must(err)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(fmt.Errorf("request error: %w", err))
return
}
if res.StatusCode != http.StatusOK {
t.Error(fmt.Errorf("unexpected status code: %s", res.Status))
}
}()
})
l, err := net.Listen("tcp", ":0")
must(err)
go func() {
s := &http.Server{
Handler: r,
}
must(s.Serve(l))
}()
addr := strings.Split(l.Addr().String(), ":")
res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/", addr[len(addr)-1]))
if err != nil {
t.Error(fmt.Errorf("request error: %w", err))
return
}
close(ensureRequestIsOver)
if res.StatusCode != http.StatusOK {
t.Error(fmt.Errorf("unexpected status code: %s", res.Status))
return
}
wg.Wait()
}
func TestContextAddParam(t *testing.T) {
c := &Context{}
id := "id"
value := "1"
c.AddParam(id, value)
v, ok := c.Params.Get(id)
assert.Equal(t, ok, true)
assert.Equal(t, value, v)
}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,7 +12,7 @@ import (
"strings" "strings"
) )
const ginSupportMinGoVer = 10 const ginSupportMinGoVer = 15
// IsDebugging returns true if the framework is running in debug mode. // IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.ReleaseMode) to disable debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode.
@ -47,7 +47,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
} }
} }
func debugPrint(format string, values ...interface{}) { func debugPrint(format string, values ...any) {
if IsDebugging() { if IsDebugging() {
if !strings.HasSuffix(format, "\n") { if !strings.HasSuffix(format, "\n") {
format += "\n" format += "\n"
@ -66,8 +66,8 @@ func getMinVer(v string) (uint64, error) {
} }
func debugPrintWARNINGDefault() { func debugPrintWARNINGDefault() {
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon. debugPrint(`[WARNING] Now Gin requires Go 1.15+.
`) `)
} }
@ -95,9 +95,7 @@ at initialization. ie. before any route is registered or the router is listening
} }
func debugPrintError(err error) { func debugPrintError(err error) {
if err != nil { if err != nil && IsDebugging() {
if IsDebugging() { fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
}
} }
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -103,8 +103,8 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
SetMode(TestMode) SetMode(TestMode)
}) })
m, e := getMinVer(runtime.Version()) m, e := getMinVer(runtime.Version())
if e == nil && m <= ginSupportMinGoVer { if e == nil && m < ginSupportMinGoVer {
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
} else { } else {
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,7 +12,7 @@ import (
// BindWith binds the passed struct pointer using the specified binding engine. // BindWith binds the passed struct pointer using the specified binding engine.
// See the binding package. // See the binding package.
func (c *Context) BindWith(obj interface{}, b binding.Binding) error { func (c *Context) BindWith(obj any, b binding.Binding) error {
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
be deprecated, please check issue #662 and either use MustBindWith() if you be deprecated, please check issue #662 and either use MustBindWith() if you
want HTTP 400 to be automatically returned if any error occur, or use want HTTP 400 to be automatically returned if any error occur, or use

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -34,7 +34,7 @@ const (
type Error struct { type Error struct {
Err error Err error
Type ErrorType Type ErrorType
Meta interface{} Meta any
} }
type errorMsgs []*Error type errorMsgs []*Error
@ -48,13 +48,13 @@ func (msg *Error) SetType(flags ErrorType) *Error {
} }
// SetMeta sets the error's meta data. // SetMeta sets the error's meta data.
func (msg *Error) SetMeta(data interface{}) *Error { func (msg *Error) SetMeta(data any) *Error {
msg.Meta = data msg.Meta = data
return msg return msg
} }
// JSON creates a properly formatted JSON // JSON creates a properly formatted JSON
func (msg *Error) JSON() interface{} { func (msg *Error) JSON() any {
jsonData := H{} jsonData := H{}
if msg.Meta != nil { if msg.Meta != nil {
value := reflect.ValueOf(msg.Meta) value := reflect.ValueOf(msg.Meta)
@ -122,7 +122,7 @@ func (a errorMsgs) Last() *Error {
return nil return nil
} }
// Errors returns an array will all the error messages. // Errors returns an array with all the error messages.
// Example: // Example:
// c.Error(errors.New("first")) // c.Error(errors.New("first"))
// c.Error(errors.New("second")) // c.Error(errors.New("second"))
@ -139,14 +139,14 @@ func (a errorMsgs) Errors() []string {
return errorStrings return errorStrings
} }
func (a errorMsgs) JSON() interface{} { func (a errorMsgs) JSON() any {
switch length := len(a); length { switch length := len(a); length {
case 0: case 0:
return nil return nil
case 1: case 1:
return a.Last().JSON() return a.Last().JSON()
default: default:
jsonData := make([]interface{}, length) jsonData := make([]any, length)
for i, err := range a { for i, err := range a {
jsonData[i] = err.JSON() jsonData[i] = err.JSON()
} }

View File

@ -1,33 +0,0 @@
// +build go1.13
package gin
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
type TestErr string
func (e TestErr) Error() string { return string(e) }
// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13,
// hence the "// +build go1.13" directive at the beginning of this file.
func TestErrorUnwrap(t *testing.T) {
innerErr := TestErr("somme error")
// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
err := fmt.Errorf("wrapped: %w", &Error{
Err: innerErr,
Type: ErrorTypeAny,
})
// check that 'errors.Is()' and 'errors.As()' behave as expected :
assert.True(t, errors.Is(err, innerErr))
var testErr TestErr
assert.True(t, errors.As(err, &testErr))
}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,6 +6,7 @@ package gin
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
@ -85,7 +86,7 @@ Error #02: second
Error #03: third Error #03: third
Meta: map[status:400] Meta: map[status:400]
`, errs.String()) `, errs.String())
assert.Equal(t, []interface{}{ assert.Equal(t, []any{
H{"error": "first"}, H{"error": "first"},
H{"error": "second", "meta": "some data"}, H{"error": "second", "meta": "some data"},
H{"error": "third", "status": "400"}, H{"error": "third", "status": "400"},
@ -104,3 +105,24 @@ Error #03: third
assert.Nil(t, errs.JSON()) assert.Nil(t, errs.JSON())
assert.Empty(t, errs.String()) assert.Empty(t, errs.String())
} }
type TestErr string
func (e TestErr) Error() string { return string(e) }
// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13.
func TestErrorUnwrap(t *testing.T) {
innerErr := TestErr("some error")
// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
err := fmt.Errorf("wrapped: %w", &Error{
Err: innerErr,
Type: ErrorTypeAny,
})
// check that 'errors.Is()' and 'errors.As()' behave as expected :
assert.True(t, errors.Is(err, innerErr))
var testErr TestErr
assert.True(t, errors.As(err, &testErr))
}

4
fs.go
View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -17,7 +17,7 @@ type neuteredReaddirFile struct {
http.File http.File
} }
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally // Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
// in router.Static(). // in router.Static().
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns // if listDirectory == true, then it works the same as http.Dir() otherwise it returns
// a filesystem that prevents http.FileServer() to list the directory files. // a filesystem that prevents http.FileServer() to list the directory files.

268
gin.go
View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,10 +11,13 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"sync" "sync"
"github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/bytesconv"
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
) )
const defaultMultipartMemory = 32 << 20 // 32 MB const defaultMultipartMemory = 32 << 20 // 32 MB
@ -24,15 +27,26 @@ var (
default405Body = []byte("405 method not allowed") default405Body = []byte("405 method not allowed")
) )
var defaultAppEngine bool var defaultPlatform string
var defaultTrustedCIDRs = []*net.IPNet{
{ // 0.0.0.0/0 (IPv4)
IP: net.IP{0x0, 0x0, 0x0, 0x0},
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
},
{ // ::/0 (IPv6)
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
},
}
// HandlerFunc defines the handler used by gin middleware as return value. // HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context) type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array. // HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc type HandlersChain []HandlerFunc
// Last returns the last handler in the chain. ie. the last handler is the main one. // Last returns the last handler in the chain. i.e. the last handler is the main one.
func (c HandlersChain) Last() HandlerFunc { func (c HandlersChain) Last() HandlerFunc {
if length := len(c); length > 0 { if length := len(c); length > 0 {
return c[length-1] return c[length-1]
@ -48,22 +62,32 @@ type RouteInfo struct {
HandlerFunc HandlerFunc HandlerFunc HandlerFunc
} }
// RoutesInfo defines a RouteInfo array. // RoutesInfo defines a RouteInfo slice.
type RoutesInfo []RouteInfo type RoutesInfo []RouteInfo
// Trusted platforms
const (
// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
// for determining the client's IP
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
// the client's IP
PlatformCloudflare = "CF-Connecting-IP"
)
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default() // Create an instance of Engine, by using New() or Default()
type Engine struct { type Engine struct {
RouterGroup RouterGroup
// Enables automatic redirection if the current route can't be matched but a // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists. // handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the // For example if /foo/ is requested but a route only exists for /foo, the
// client is redirected to /foo with http status code 301 for GET requests // client is redirected to /foo with http status code 301 for GET requests
// and 307 for all other request methods. // and 307 for all other request methods.
RedirectTrailingSlash bool RedirectTrailingSlash bool
// If enabled, the router tries to fix the current request path, if no // RedirectFixedPath if enabled, the router tries to fix the current request path, if no
// handle is registered for it. // handle is registered for it.
// First superfluous path elements like ../ or // are removed. // First superfluous path elements like ../ or // are removed.
// Afterwards the router does a case-insensitive lookup of the cleaned path. // Afterwards the router does a case-insensitive lookup of the cleaned path.
@ -74,35 +98,58 @@ type Engine struct {
// RedirectTrailingSlash is independent of this option. // RedirectTrailingSlash is independent of this option.
RedirectFixedPath bool RedirectFixedPath bool
// If enabled, the router checks if another method is allowed for the // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed. // current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed' // If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405. // and HTTP status code 405.
// If no other Method is allowed, the request is delegated to the NotFound // If no other Method is allowed, the request is delegated to the NotFound
// handler. // handler.
HandleMethodNotAllowed bool HandleMethodNotAllowed bool
ForwardedByClientIP bool
// #726 #755 If enabled, it will thrust some headers starting with // 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
// AppEngine was deprecated.
// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
// #726 #755 If enabled, it will trust some headers starting with
// 'X-AppEngine...' for better integration with that PaaS. // 'X-AppEngine...' for better integration with that PaaS.
AppEngine bool AppEngine bool
// If enabled, the url.RawPath will be used to find parameters. // UseRawPath if enabled, the url.RawPath will be used to find parameters.
UseRawPath bool UseRawPath bool
// If true, the path value will be unescaped. // UnescapePathValues if true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true, // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped. // as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool UnescapePathValues bool
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call.
MaxMultipartMemory int64
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
// See the PR #1817 and issue #1644 // See the PR #1817 and issue #1644
RemoveExtraSlash bool RemoveExtraSlash bool
// RemoteIPHeaders list of headers used to obtain the client IP when
// `(*gin.Engine).ForwardedByClientIP` is `true` and
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
RemoteIPHeaders []string
// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
// that platform, for example to determine the client IP
TrustedPlatform string
// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call.
MaxMultipartMemory int64
// UseH2C enable h2c support.
UseH2C bool
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
ContextWithFallback bool
delims render.Delims delims render.Delims
secureJSONPrefix string secureJSONPrefix string
HTMLRender render.HTMLRender HTMLRender render.HTMLRender
@ -114,12 +161,15 @@ type Engine struct {
pool sync.Pool pool sync.Pool
trees methodTrees trees methodTrees
maxParams uint16 maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
} }
var _ IRouter = &Engine{} var _ IRouter = &Engine{}
// New returns a new blank Engine instance without any middleware attached. // New returns a new blank Engine instance without any middleware attached.
// By default the configuration is: // By default, the configuration is:
// - RedirectTrailingSlash: true // - RedirectTrailingSlash: true
// - RedirectFixedPath: false // - RedirectFixedPath: false
// - HandleMethodNotAllowed: false // - HandleMethodNotAllowed: false
@ -139,7 +189,8 @@ func New() *Engine {
RedirectFixedPath: false, RedirectFixedPath: false,
HandleMethodNotAllowed: false, HandleMethodNotAllowed: false,
ForwardedByClientIP: true, ForwardedByClientIP: true,
AppEngine: defaultAppEngine, RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false, UseRawPath: false,
RemoveExtraSlash: false, RemoveExtraSlash: false,
UnescapePathValues: true, UnescapePathValues: true,
@ -147,9 +198,11 @@ func New() *Engine {
trees: make(methodTrees, 0, 9), trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"}, delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);", secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
} }
engine.RouterGroup.engine = engine engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} { engine.pool.New = func() any {
return engine.allocateContext() return engine.allocateContext()
} }
return engine return engine
@ -163,12 +216,22 @@ func Default() *Engine {
return engine return engine
} }
func (engine *Engine) allocateContext() *Context { func (engine *Engine) Handler() http.Handler {
v := make(Params, 0, engine.maxParams) if !engine.UseH2C {
return &Context{engine: engine, params: &v} return engine
}
h2s := &http2.Server{}
return h2c.NewHandler(engine, h2s)
} }
// Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) allocateContext() *Context {
v := make(Params, 0, engine.maxParams)
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}
// Delims sets template left and right delims and returns an Engine instance.
func (engine *Engine) Delims(left, right string) *Engine { func (engine *Engine) Delims(left, right string) *Engine {
engine.delims = render.Delims{Left: left, Right: right} engine.delims = render.Delims{Left: left, Right: right}
return engine return engine
@ -222,19 +285,19 @@ func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.FuncMap = funcMap engine.FuncMap = funcMap
} }
// NoRoute adds handlers for NoRoute. It return a 404 code by default. // NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc) { func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.noRoute = handlers engine.noRoute = handlers
engine.rebuild404Handlers() engine.rebuild404Handlers()
} }
// NoMethod sets the handlers called when... TODO. // NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) { func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers engine.noMethod = handlers
engine.rebuild405Handlers() engine.rebuild405Handlers()
} }
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be // Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files... // included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware. // For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
@ -271,6 +334,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
if paramsCount := countParams(path); paramsCount > engine.maxParams { if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount engine.maxParams = paramsCount
} }
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
} }
// Routes returns a slice of registered routes, including some useful information, such as: // Routes returns a slice of registered routes, including some useful information, such as:
@ -305,12 +372,120 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
func (engine *Engine) Run(addr ...string) (err error) { func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr) address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address) debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine) err = http.ListenAndServe(address, engine.Handler())
return return
} }
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
if engine.trustedProxies == nil {
return nil, nil
}
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
for _, trustedProxy := range engine.trustedProxies {
if !strings.Contains(trustedProxy, "/") {
ip := parseIP(trustedProxy)
if ip == nil {
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
}
switch len(ip) {
case net.IPv4len:
trustedProxy += "/32"
case net.IPv6len:
trustedProxy += "/128"
}
}
_, cidrNet, err := net.ParseCIDR(trustedProxy)
if err != nil {
return cidr, err
}
cidr = append(cidr, cidrNet)
}
return cidr, nil
}
// SetTrustedProxies set a list of network origins (IPv4 addresses,
// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
// request's headers that contain alternative client IP when
// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
// feature is enabled by default, and it also trusts all proxies
// by default. If you want to disable this feature, use
// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
// return the remote address directly.
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
engine.trustedProxies = trustedProxies
return engine.parseTrustedProxies()
}
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
func (engine *Engine) isUnsafeTrustedProxies() bool {
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
}
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
func (engine *Engine) parseTrustedProxies() error {
trustedCIDRs, err := engine.prepareTrustedCIDRs()
engine.trustedCIDRs = trustedCIDRs
return err
}
// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
func (engine *Engine) isTrustedProxy(ip net.IP) bool {
if engine.trustedCIDRs == nil {
return false
}
for _, cidr := range engine.trustedCIDRs {
if cidr.Contains(ip) {
return true
}
}
return false
}
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
break
}
// X-Forwarded-For is appended by proxy
// Check IPs in reverse order and stop when find untrusted proxy
if (i == 0) || (!engine.isTrustedProxy(ip)) {
return ipStr, true
}
}
return "", false
}
// parseIP parse a string representation of an IP and returns a net.IP with the
// minimum byte representation or nil if input is invalid.
func parseIP(ip string) net.IP {
parsedIP := net.ParseIP(ip)
if ipv4 := parsedIP.To4(); ipv4 != nil {
// return ip in a 4-byte representation
return ipv4
}
// return ip in a 16-byte representation or nil
return parsedIP
}
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
@ -318,17 +493,27 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
debugPrint("Listening and serving HTTPS on %s\n", addr) debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
return return
} }
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
// through the specified unix socket (ie. a file). // through the specified unix socket (i.e. a file).
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) RunUnix(file string) (err error) { func (engine *Engine) RunUnix(file string) (err error) {
debugPrint("Listening and serving HTTP on unix:/%s", file) debugPrint("Listening and serving HTTP on unix:/%s", file)
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
listener, err := net.Listen("unix", file) listener, err := net.Listen("unix", file)
if err != nil { if err != nil {
return return
@ -336,7 +521,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
defer listener.Close() defer listener.Close()
defer os.Remove(file) defer os.Remove(file)
err = http.Serve(listener, engine) err = http.Serve(listener, engine.Handler())
return return
} }
@ -347,6 +532,11 @@ func (engine *Engine) RunFd(fd int) (err error) {
debugPrint("Listening and serving HTTP on fd@%d", fd) debugPrint("Listening and serving HTTP on fd@%d", fd)
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
listener, err := net.FileListener(f) listener, err := net.FileListener(f)
if err != nil { if err != nil {
@ -362,7 +552,13 @@ func (engine *Engine) RunFd(fd int) (err error) {
func (engine *Engine) RunListener(listener net.Listener) (err error) { func (engine *Engine) RunListener(listener net.Listener) (err error) {
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
err = http.Serve(listener, engine)
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
err = http.Serve(listener, engine.Handler())
return return
} }
@ -378,9 +574,9 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
engine.pool.Put(c) engine.pool.Put(c)
} }
// HandleContext re-enter a context that has been rewritten. // HandleContext re-enters a context that has been rewritten.
// This can be done by setting c.Request.URL.Path to your new target. // This can be done by setting c.Request.URL.Path to your new target.
// Disclaimer: You can loop yourself to death with this, use wisely. // Disclaimer: You can loop yourself to deal with this, use wisely.
func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) HandleContext(c *Context) {
oldIndexValue := c.index oldIndexValue := c.index
c.reset() c.reset()
@ -410,7 +606,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
} }
root := t[i].root root := t[i].root
// Find route in tree // Find route in tree
value := root.getValue(rPath, c.params, unescape) value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil { if value.params != nil {
c.Params = *value.params c.Params = *value.params
} }
@ -421,7 +617,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
return return
} }
if httpMethod != "CONNECT" && rPath != "/" { if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash { if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c) redirectTrailingSlash(c)
return return
@ -438,7 +634,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
if tree.method == httpMethod { if tree.method == httpMethod {
continue continue
} }
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body) serveError(c, http.StatusMethodNotAllowed, default405Body)
return return

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -37,7 +37,7 @@ func SetHTMLTemplate(templ *template.Template) {
engine().SetHTMLTemplate(templ) engine().SetHTMLTemplate(templ)
} }
// NoRoute adds handlers for NoRoute. It return a 404 code by default. // NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func NoRoute(handlers ...gin.HandlerFunc) { func NoRoute(handlers ...gin.HandlerFunc) {
engine().NoRoute(handlers...) engine().NoRoute(handlers...)
} }
@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
return engine().StaticFS(relativePath, fs) return engine().StaticFS(relativePath, fs)
} }
// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be // Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files... // included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware. // For example, this is the right place for a logger or error management middleware.
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
@ -145,7 +145,7 @@ func RunTLS(addr, certFile, keyFile string) (err error) {
} }
// RunUnix attaches to a http.Server and starts listening and serving HTTP requests // RunUnix attaches to a http.Server and starts listening and serving HTTP requests
// through the specified unix socket (ie. a file) // through the specified unix socket (i.e. a file)
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
func RunUnix(file string) (err error) { func RunUnix(file string) (err error) {
return engine().RunUnix(file) return engine().RunUnix(file)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -14,6 +14,8 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath"
"runtime"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -21,7 +23,15 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func testRequest(t *testing.T, url string) { // params[0]=url example:http://127.0.0.1:8080/index (cannot be empty)
// params[1]=response status (custom compare status) default:"200 OK"
// params[2]=response body (custom compare content) default:"it worked"
func testRequest(t *testing.T, params ...string) {
if len(params) == 0 {
t.Fatal("url cannot be empty")
}
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
@ -29,14 +39,27 @@ func testRequest(t *testing.T, url string) {
} }
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
resp, err := client.Get(url) resp, err := client.Get(params[0])
assert.NoError(t, err) assert.NoError(t, err)
defer resp.Body.Close() defer resp.Body.Close()
body, ioerr := ioutil.ReadAll(resp.Body) body, ioerr := ioutil.ReadAll(resp.Body)
assert.NoError(t, ioerr) assert.NoError(t, ioerr)
assert.Equal(t, "it worked", string(body), "resp body should match")
assert.Equal(t, "200 OK", resp.Status, "should get a 200") var responseStatus = "200 OK"
if len(params) > 1 && params[1] != "" {
responseStatus = params[1]
}
var responseBody = "it worked"
if len(params) > 2 && params[2] != "" {
responseBody = params[2]
}
assert.Equal(t, responseStatus, resp.Status, "should get a "+responseStatus)
if responseStatus == "200 OK" {
assert.Equal(t, responseBody, string(body), "resp body should match")
}
} }
func TestRunEmpty(t *testing.T) { func TestRunEmpty(t *testing.T) {
@ -54,6 +77,81 @@ func TestRunEmpty(t *testing.T) {
testRequest(t, "http://localhost:8080/example") testRequest(t, "http://localhost:8080/example")
} }
func TestBadTrustedCIDRs(t *testing.T) {
router := New()
assert.Error(t, router.SetTrustedProxies([]string{"hello/world"}))
}
/* legacy tests
func TestBadTrustedCIDRsForRun(t *testing.T) {
os.Setenv("PORT", "")
router := New()
router.TrustedProxies = []string{"hello/world"}
assert.Error(t, router.Run(":8080"))
}
func TestBadTrustedCIDRsForRunUnix(t *testing.T) {
router := New()
router.TrustedProxies = []string{"hello/world"}
unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test")
defer os.Remove(unixTestSocket)
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
assert.Error(t, router.RunUnix(unixTestSocket))
}()
// have to wait for the goroutine to start and run the server
// otherwise the main thread will complete
time.Sleep(5 * time.Millisecond)
}
func TestBadTrustedCIDRsForRunFd(t *testing.T) {
router := New()
router.TrustedProxies = []string{"hello/world"}
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
assert.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err)
socketFile, err := listener.File()
assert.NoError(t, err)
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
assert.Error(t, router.RunFd(int(socketFile.Fd())))
}()
// have to wait for the goroutine to start and run the server
// otherwise the main thread will complete
time.Sleep(5 * time.Millisecond)
}
func TestBadTrustedCIDRsForRunListener(t *testing.T) {
router := New()
router.TrustedProxies = []string{"hello/world"}
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
assert.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err)
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
assert.Error(t, router.RunListener(listener))
}()
// have to wait for the goroutine to start and run the server
// otherwise the main thread will complete
time.Sleep(5 * time.Millisecond)
}
func TestBadTrustedCIDRsForRunTLS(t *testing.T) {
os.Setenv("PORT", "")
router := New()
router.TrustedProxies = []string{"hello/world"}
assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
}
*/
func TestRunTLS(t *testing.T) { func TestRunTLS(t *testing.T) {
router := New() router := New()
go func() { go func() {
@ -146,7 +244,7 @@ func TestRunWithPort(t *testing.T) {
func TestUnixSocket(t *testing.T) { func TestUnixSocket(t *testing.T) {
router := New() router := New()
unixTestSocket := "/tmp/unix_unit_test" unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test")
defer os.Remove(unixTestSocket) defer os.Remove(unixTestSocket)
@ -184,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
listener, err := net.ListenTCP("tcp", addr) listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err) assert.NoError(t, err)
socketFile, err := listener.File() socketFile, err := listener.File()
assert.NoError(t, err) if isWindows() {
// not supported by windows, it is unimplemented now
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
if socketFile == nil {
return
}
go func() { go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@ -304,3 +411,153 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
assert.Equal(t, "it worked", w.Body.String(), "resp body should match") assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
assert.Equal(t, 200, w.Code, "should get a 200") assert.Equal(t, 200, w.Code, "should get a 200")
} }
func TestTreeRunDynamicRouting(t *testing.T) {
router := New()
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") })
router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") })
router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") })
router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") })
router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") })
router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") })
router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") })
router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") })
router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") })
router.GET("/get/abc", func(c *Context) { c.String(http.StatusOK, "/get/abc") })
router.GET("/get/:param", func(c *Context) { c.String(http.StatusOK, "/get/:param") })
router.GET("/get/abc/123abc", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc") })
router.GET("/get/abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param") })
router.GET("/get/abc/123abc/xxx8", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8") })
router.GET("/get/abc/123abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/:param") })
router.GET("/get/abc/123abc/xxx8/1234", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234") })
router.GET("/get/abc/123abc/xxx8/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/:param") })
router.GET("/get/abc/123abc/xxx8/1234/ffas", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/ffas") })
router.GET("/get/abc/123abc/xxx8/1234/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/:param") })
router.GET("/get/abc/123abc/xxx8/1234/kkdd/12c", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/12c") })
router.GET("/get/abc/123abc/xxx8/1234/kkdd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/:param") })
router.GET("/get/abc/:param/test", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param/test") })
router.GET("/get/abc/123abd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abd/:param") })
router.GET("/get/abc/123abddd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abddd/:param") })
router.GET("/get/abc/123/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123/:param") })
router.GET("/get/abc/123abg/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abg/:param") })
router.GET("/get/abc/123abf/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abf/:param") })
router.GET("/get/abc/123abfff/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abfff/:param") })
ts := httptest.NewServer(router)
defer ts.Close()
testRequest(t, ts.URL+"/", "", "home")
testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx")
testRequest(t, ts.URL+"/ab/ab", "", "/ab/*xx")
testRequest(t, ts.URL+"/all", "", "/:cc")
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e")
testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1")
testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee")
testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f")
testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee")
testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff")
testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg")
testRequest(t, ts.URL+"/c/d/e/f/g/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
testRequest(t, ts.URL+"/a", "", "/:cc")
testRequest(t, ts.URL+"/d", "", "/:cc")
testRequest(t, ts.URL+"/ad", "", "/:cc")
testRequest(t, ts.URL+"/dd", "", "/:cc")
testRequest(t, ts.URL+"/aa", "", "/:cc")
testRequest(t, ts.URL+"/aaa", "", "/:cc")
testRequest(t, ts.URL+"/aaa/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/ab", "", "/:cc")
testRequest(t, ts.URL+"/abb", "", "/:cc")
testRequest(t, ts.URL+"/abb/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/dddaa", "", "/:cc")
testRequest(t, ts.URL+"/allxxxx", "", "/:cc")
testRequest(t, ts.URL+"/alldd", "", "/:cc")
testRequest(t, ts.URL+"/cc/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/ccc/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/deedwjfs/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/acllcc/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/get/test/abc/", "", "/get/test/abc/")
testRequest(t, ts.URL+"/get/testaa/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/te/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/xx/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/tt/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/a/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/t/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/")
testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test")
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing")
testRequest(t, ts.URL+"/something/se/thirdthing", "", "/something/:paramname/thirdthing")
testRequest(t, ts.URL+"/something/s/thirdthing", "", "/something/:paramname/thirdthing")
testRequest(t, ts.URL+"/something/secondthing/thirdthing", "", "/something/:paramname/thirdthing")
testRequest(t, ts.URL+"/get/abc", "", "/get/abc")
testRequest(t, ts.URL+"/get/a", "", "/get/:param")
testRequest(t, ts.URL+"/get/abz", "", "/get/:param")
testRequest(t, ts.URL+"/get/12a", "", "/get/:param")
testRequest(t, ts.URL+"/get/abcd", "", "/get/:param")
testRequest(t, ts.URL+"/get/abc/123abc", "", "/get/abc/123abc")
testRequest(t, ts.URL+"/get/abc/12", "", "/get/abc/:param")
testRequest(t, ts.URL+"/get/abc/123ab", "", "/get/abc/:param")
testRequest(t, ts.URL+"/get/abc/xyz", "", "/get/abc/:param")
testRequest(t, ts.URL+"/get/abc/123abcddxx", "", "/get/abc/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8", "", "/get/abc/123abc/xxx8")
testRequest(t, ts.URL+"/get/abc/123abc/x", "", "/get/abc/123abc/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx", "", "/get/abc/123abc/:param")
testRequest(t, ts.URL+"/get/abc/123abc/abc", "", "/get/abc/123abc/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8xxas", "", "/get/abc/123abc/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234", "", "/get/abc/123abc/xxx8/1234")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1", "", "/get/abc/123abc/xxx8/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/123", "", "/get/abc/123abc/xxx8/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/78k", "", "/get/abc/123abc/xxx8/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234xxxd", "", "/get/abc/123abc/xxx8/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas", "", "/get/abc/123abc/xxx8/1234/ffas")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/f", "", "/get/abc/123abc/xxx8/1234/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffa", "", "/get/abc/123abc/xxx8/1234/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kka", "", "/get/abc/123abc/xxx8/1234/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas321", "", "/get/abc/123abc/xxx8/1234/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c", "", "/get/abc/123abc/xxx8/1234/kkdd/12c")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/1", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12b", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/34", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
testRequest(t, ts.URL+"/get/abc/12/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abdd/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abdddf/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123ab/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abgg/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abff/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abffff/test", "", "/get/abc/:param/test")
testRequest(t, ts.URL+"/get/abc/123abd/test", "", "/get/abc/123abd/:param")
testRequest(t, ts.URL+"/get/abc/123abddd/test", "", "/get/abc/123abddd/:param")
testRequest(t, ts.URL+"/get/abc/123/test22", "", "/get/abc/123/:param")
testRequest(t, ts.URL+"/get/abc/123abg/test", "", "/get/abc/123abg/:param")
testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
// 404 not found
testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found")
testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found")
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found")
testRequest(t, ts.URL+"/a/dd", "404 Not Found")
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
}
func isWindows() bool {
return runtime.GOOS == "windows"
}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
@ -18,6 +19,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/http2"
) )
func formatAsDate(t time.Time) string { func formatAsDate(t time.Time) string {
@ -41,7 +43,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
}) })
router.GET("/raw", func(c *Context) { router.GET("/raw", func(c *Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ c.HTML(http.StatusOK, "raw.tmpl", map[string]any{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
}) })
}) })
@ -78,6 +80,44 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
assert.Equal(t, "<h1>Hello world</h1>", string(resp)) assert.Equal(t, "<h1>Hello world</h1>", string(resp))
} }
func TestH2c(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
fmt.Println(err)
}
r := Default()
r.UseH2C = true
r.GET("/", func(c *Context) {
c.String(200, "<h1>Hello world</h1>")
})
go func() {
err := http.Serve(ln, r.Handler())
if err != nil {
fmt.Println(err)
}
}()
defer ln.Close()
url := "http://" + ln.Addr().String() + "/"
http := http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
},
}
res, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
}
func TestLoadHTMLGlobTestMode(t *testing.T) { func TestLoadHTMLGlobTestMode(t *testing.T) {
ts := setupHTMLFiles( ts := setupHTMLFiles(
t, t,
@ -162,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
} }
resp, _ := ioutil.ReadAll(res.Body) resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "Date: 2017/07/01\n", string(resp)) assert.Equal(t, "Date: 2017/07/01", string(resp))
} }
func init() { func init() {
@ -280,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
} }
resp, _ := ioutil.ReadAll(res.Body) resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "Date: 2017/07/01\n", string(resp)) assert.Equal(t, "Date: 2017/07/01", string(resp))
} }
func TestAddRoute(t *testing.T) { func TestAddRoute(t *testing.T) {
@ -394,7 +434,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
} }
func TestRebuild404Handlers(t *testing.T) { func TestRebuild404Handlers(t *testing.T) {
} }
func TestNoMethodWithGlobalHandlers(t *testing.T) { func TestNoMethodWithGlobalHandlers(t *testing.T) {
@ -428,7 +467,7 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) {
compareFunc(t, router.allNoMethod[2], middleware0) compareFunc(t, router.allNoMethod[2], middleware0)
} }
func compareFunc(t *testing.T, a, b interface{}) { func compareFunc(t *testing.T, a, b any) {
sf1 := reflect.ValueOf(a) sf1 := reflect.ValueOf(a)
sf2 := reflect.ValueOf(b) sf2 := reflect.ValueOf(b)
if sf1.Pointer() != sf2.Pointer() { if sf1.Pointer() != sf2.Pointer() {
@ -490,7 +529,7 @@ func TestEngineHandleContext(t *testing.T) {
} }
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
w := performRequest(r, "GET", "/") w := PerformRequest(r, "GET", "/")
assert.Equal(t, 301, w.Code) assert.Equal(t, 301, w.Code)
}) })
} }
@ -523,7 +562,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
}) })
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
assert.Equal(t, 200, w.Code) assert.Equal(t, 200, w.Code)
assert.Equal(t, expectValue, w.Body.Len()) assert.Equal(t, expectValue, w.Body.Len())
}) })
@ -532,6 +571,119 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
assert.Equal(t, int64(expectValue), middlewareCounter) assert.Equal(t, int64(expectValue), middlewareCounter)
} }
func TestPrepareTrustedCIRDsWith(t *testing.T) {
r := New()
// valid ipv4 cidr
{
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")}
err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
assert.NoError(t, err)
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
}
// invalid ipv4 cidr
{
err := r.SetTrustedProxies([]string{"192.168.1.33/33"})
assert.Error(t, err)
}
// valid ipv4 address
{
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")}
err := r.SetTrustedProxies([]string{"192.168.1.33"})
assert.NoError(t, err)
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
}
// invalid ipv4 address
{
err := r.SetTrustedProxies([]string{"192.168.1.256"})
assert.Error(t, err)
}
// valid ipv6 address
{
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
assert.NoError(t, err)
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
}
// invalid ipv6 address
{
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"})
assert.Error(t, err)
}
// valid ipv6 cidr
{
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")}
err := r.SetTrustedProxies([]string{"::/0"})
assert.NoError(t, err)
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
}
// invalid ipv6 cidr
{
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"})
assert.Error(t, err)
}
// valid combination
{
expectedTrustedCIDRs := []*net.IPNet{
parseCIDR("::/0"),
parseCIDR("192.168.0.0/16"),
parseCIDR("172.16.0.1/32"),
}
err := r.SetTrustedProxies([]string{
"::/0",
"192.168.0.0/16",
"172.16.0.1",
})
assert.NoError(t, err)
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
}
// invalid combination
{
err := r.SetTrustedProxies([]string{
"::/0",
"192.168.0.0/16",
"172.16.0.256",
})
assert.Error(t, err)
}
// nil value
{
err := r.SetTrustedProxies(nil)
assert.Nil(t, r.trustedCIDRs)
assert.Nil(t, err)
}
}
func parseCIDR(cidr string) *net.IPNet {
_, parsedCIDR, err := net.ParseCIDR(cidr)
if err != nil {
fmt.Println(err)
}
return parsedCIDR
}
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
for _, gotRoute := range gotRoutes { for _, gotRoute := range gotRoutes {
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -296,13 +296,13 @@ func TestShouldBindUri(t *testing.T) {
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
var person Person var person Person
assert.NoError(t, c.ShouldBindUri(&person)) assert.NoError(t, c.ShouldBindUri(&person))
assert.True(t, "" != person.Name) assert.True(t, person.Name != "")
assert.True(t, "" != person.ID) assert.True(t, person.ID != "")
c.String(http.StatusOK, "ShouldBindUri test OK") c.String(http.StatusOK, "ShouldBindUri test OK")
}) })
path, _ := exampleFromPath("/rest/:name/:id") path, _ := exampleFromPath("/rest/:name/:id")
w := performRequest(router, http.MethodGet, path) w := PerformRequest(router, http.MethodGet, path)
assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
@ -318,13 +318,13 @@ func TestBindUri(t *testing.T) {
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
var person Person var person Person
assert.NoError(t, c.BindUri(&person)) assert.NoError(t, c.BindUri(&person))
assert.True(t, "" != person.Name) assert.True(t, person.Name != "")
assert.True(t, "" != person.ID) assert.True(t, person.ID != "")
c.String(http.StatusOK, "BindUri test OK") c.String(http.StatusOK, "BindUri test OK")
}) })
path, _ := exampleFromPath("/rest/:name/:id") path, _ := exampleFromPath("/rest/:name/:id")
w := performRequest(router, http.MethodGet, path) w := PerformRequest(router, http.MethodGet, path)
assert.Equal(t, "BindUri test OK", w.Body.String()) assert.Equal(t, "BindUri test OK", w.Body.String())
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
@ -342,7 +342,7 @@ func TestBindUriError(t *testing.T) {
}) })
path1, _ := exampleFromPath("/new/rest/:num") path1, _ := exampleFromPath("/new/rest/:num")
w1 := performRequest(router, http.MethodGet, path1) w1 := PerformRequest(router, http.MethodGet, path1)
assert.Equal(t, http.StatusBadRequest, w1.Code) assert.Equal(t, http.StatusBadRequest, w1.Code)
} }
@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) {
go readWriteKeys(c.Copy()) go readWriteKeys(c.Copy())
c.String(http.StatusOK, "run OK, no panics") c.String(http.StatusOK, "run OK, no panics")
}) })
w := performRequest(router, http.MethodGet, "/test/copy/race") w := PerformRequest(router, http.MethodGet, "/test/copy/race")
assert.Equal(t, "run OK, no panics", w.Body.String()) assert.Equal(t, "run OK, no panics", w.Body.String())
} }
@ -389,7 +389,7 @@ func TestGithubAPI(t *testing.T) {
for _, route := range githubAPI { for _, route := range githubAPI {
path, values := exampleFromPath(route.path) path, values := exampleFromPath(route.path)
w := performRequest(router, route.method, path) w := PerformRequest(router, route.method, path)
// TEST // TEST
assert.Contains(t, w.Body.String(), "\"status\":\"good\"") assert.Contains(t, w.Body.String(), "\"status\":\"good\"")

33
go.mod
View File

@ -1,14 +1,31 @@
module github.com/gin-gonic/gin module github.com/gin-gonic/gin
go 1.13 go 1.18
require ( require (
github.com/gin-contrib/sse v0.1.0 github.com/gin-contrib/sse v0.1.0
github.com/go-playground/validator/v10 v10.4.1 github.com/go-playground/validator/v10 v10.10.0
github.com/golang/protobuf v1.3.3 github.com/goccy/go-json v0.9.8
github.com/json-iterator/go v1.1.9 github.com/json-iterator/go v1.1.12
github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-isatty v0.0.14
github.com/stretchr/testify v1.4.0 github.com/pelletier/go-toml/v2 v2.0.2
github.com/ugorji/go/codec v1.1.7 github.com/stretchr/testify v1.8.0
gopkg.in/yaml.v2 v2.2.8 github.com/ugorji/go/codec v1.2.7
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

108
go.sum
View File

@ -1,3 +1,4 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -5,48 +6,83 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -5,16 +5,17 @@
package bytesconv package bytesconv
import ( import (
"reflect"
"unsafe" "unsafe"
) )
// StringToBytes converts string to byte slice without a memory allocation. // StringToBytes converts string to byte slice without a memory allocation.
func StringToBytes(s string) (b []byte) { func StringToBytes(s string) []byte {
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) return *(*[]byte)(unsafe.Pointer(
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) &struct {
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len string
return b Cap int
}{s, len(s)},
))
} }
// BytesToString converts byte slice to string without a memory allocation. // BytesToString converts byte slice to string without a memory allocation.

23
internal/json/go_json.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2017 Bo-Yi Wu. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build go_json
// +build go_json
package json
import json "github.com/goccy/go-json"
var (
// Marshal is exported by gin/json package.
Marshal = json.Marshal
// Unmarshal is exported by gin/json package.
Unmarshal = json.Unmarshal
// MarshalIndent is exported by gin/json package.
MarshalIndent = json.MarshalIndent
// NewDecoder is exported by gin/json package.
NewDecoder = json.NewDecoder
// NewEncoder is exported by gin/json package.
NewEncoder = json.NewEncoder
)

View File

@ -1,8 +1,9 @@
// 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !jsoniter //go:build !jsoniter && !go_json
// +build !jsoniter,!go_json
package json package json

View File

@ -1,7 +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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build jsoniter
// +build jsoniter // +build jsoniter
package json package json

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -44,7 +44,7 @@ type LoggerConfig struct {
// Optional. Default value is gin.DefaultWriter. // Optional. Default value is gin.DefaultWriter.
Output io.Writer Output io.Writer
// SkipPaths is a url path array which logs are not written. // SkipPaths is an url path array which logs are not written.
// Optional. // Optional.
SkipPaths []string SkipPaths []string
} }
@ -70,12 +70,12 @@ type LogFormatterParams struct {
Path string Path string
// ErrorMessage is set if error has occurred in processing the request. // ErrorMessage is set if error has occurred in processing the request.
ErrorMessage string ErrorMessage string
// isTerm shows whether does gin's output descriptor refers to a terminal. // isTerm shows whether gin's output descriptor refers to a terminal.
isTerm bool isTerm bool
// BodySize is the size of the Response Body // BodySize is the size of the Response Body
BodySize int BodySize int
// Keys are the keys set on the request's context. // Keys are the keys set on the request's context.
Keys map[string]interface{} Keys map[string]any
} }
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
@ -138,8 +138,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
} }
if param.Latency > time.Minute { if param.Latency > time.Minute {
// Truncate in a golang < 1.8 safe way param.Latency = param.Latency.Truncate(time.Second)
param.Latency = param.Latency - param.Latency%time.Second
} }
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.TimeStamp.Format("2006/01/02 - 15:04:05"),
@ -162,12 +161,12 @@ func ForceConsoleColor() {
consoleColorMode = forceColor consoleColorMode = forceColor
} }
// ErrorLogger returns a handlerfunc for any error type. // ErrorLogger returns a HandlerFunc for any error type.
func ErrorLogger() HandlerFunc { func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny) return ErrorLoggerT(ErrorTypeAny)
} }
// ErrorLoggerT returns a handlerfunc for a given error type. // ErrorLoggerT returns a HandlerFunc for a given error type.
func ErrorLoggerT(typ ErrorType) HandlerFunc { func ErrorLoggerT(typ ErrorType) HandlerFunc {
return func(c *Context) { return func(c *Context) {
c.Next() c.Next()
@ -179,7 +178,7 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
} }
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default gin.DefaultWriter = os.Stdout. // By default, gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc { func Logger() HandlerFunc {
return LoggerWithConfig(LoggerConfig{}) return LoggerWithConfig(LoggerConfig{})
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -31,7 +31,7 @@ func TestLogger(t *testing.T) {
router.HEAD("/example", func(c *Context) {}) router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {})
performRequest(router, "GET", "/example?a=100") PerformRequest(router, "GET", "/example?a=100")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
@ -41,43 +41,43 @@ func TestLogger(t *testing.T) {
// like integration tests because they test the whole logging process rather // like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go. // than individual functions. Im not sure where these should go.
buffer.Reset() buffer.Reset()
performRequest(router, "POST", "/example") PerformRequest(router, "POST", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "POST")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "PUT", "/example") PerformRequest(router, "PUT", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "PUT")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "DELETE", "/example") PerformRequest(router, "DELETE", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "DELETE")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "PATCH", "/example") PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "HEAD", "/example") PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "OPTIONS", "/example") PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "GET", "/notfound") PerformRequest(router, "GET", "/notfound")
assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/notfound") assert.Contains(t, buffer.String(), "/notfound")
@ -95,7 +95,7 @@ func TestLoggerWithConfig(t *testing.T) {
router.HEAD("/example", func(c *Context) {}) router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {})
performRequest(router, "GET", "/example?a=100") PerformRequest(router, "GET", "/example?a=100")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
@ -105,43 +105,43 @@ func TestLoggerWithConfig(t *testing.T) {
// like integration tests because they test the whole logging process rather // like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go. // than individual functions. Im not sure where these should go.
buffer.Reset() buffer.Reset()
performRequest(router, "POST", "/example") PerformRequest(router, "POST", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "POST")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "PUT", "/example") PerformRequest(router, "PUT", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "PUT")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "DELETE", "/example") PerformRequest(router, "DELETE", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "DELETE")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "PATCH", "/example") PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "HEAD", "/example") PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "OPTIONS", "/example") PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/example")
buffer.Reset() buffer.Reset()
performRequest(router, "GET", "/notfound") PerformRequest(router, "GET", "/notfound")
assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/notfound") assert.Contains(t, buffer.String(), "/notfound")
@ -169,7 +169,7 @@ func TestLoggerWithFormatter(t *testing.T) {
) )
})) }))
router.GET("/example", func(c *Context) {}) router.GET("/example", func(c *Context) {})
performRequest(router, "GET", "/example?a=100") PerformRequest(router, "GET", "/example?a=100")
// output test // output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@ -181,10 +181,12 @@ func TestLoggerWithFormatter(t *testing.T) {
func TestLoggerWithConfigFormatting(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) {
var gotParam LogFormatterParams var gotParam LogFormatterParams
var gotKeys map[string]interface{} var gotKeys map[string]any
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
router := New() router := New()
router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs()
router.Use(LoggerWithConfig(LoggerConfig{ router.Use(LoggerWithConfig(LoggerConfig{
Output: buffer, Output: buffer,
Formatter: func(param LogFormatterParams) string { Formatter: func(param LogFormatterParams) string {
@ -206,8 +208,9 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
// set dummy ClientIP // set dummy ClientIP
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
gotKeys = c.Keys gotKeys = c.Keys
time.Sleep(time.Millisecond)
}) })
performRequest(router, "GET", "/example?a=100") PerformRequest(router, "GET", "/example?a=100")
// output test // output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@ -226,7 +229,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
assert.Equal(t, "/example?a=100", gotParam.Path) assert.Equal(t, "/example?a=100", gotParam.Path)
assert.Empty(t, gotParam.ErrorMessage) assert.Empty(t, gotParam.ErrorMessage)
assert.Equal(t, gotKeys, gotParam.Keys) assert.Equal(t, gotKeys, gotParam.Keys)
} }
func TestDefaultLogFormatter(t *testing.T) { func TestDefaultLogFormatter(t *testing.T) {
@ -280,7 +282,6 @@ func TestDefaultLogFormatter(t *testing.T) {
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
} }
func TestColorForMethod(t *testing.T) { func TestColorForMethod(t *testing.T) {
@ -367,15 +368,15 @@ func TestErrorLogger(t *testing.T) {
c.String(http.StatusInternalServerError, "hola!") c.String(http.StatusInternalServerError, "hola!")
}) })
w := performRequest(router, "GET", "/error") w := PerformRequest(router, "GET", "/error")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
w = performRequest(router, "GET", "/abort") w = PerformRequest(router, "GET", "/abort")
assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
w = performRequest(router, "GET", "/print") w = PerformRequest(router, "GET", "/print")
assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
} }
@ -387,11 +388,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) {
router.GET("/logged", func(c *Context) {}) router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {}) router.GET("/skipped", func(c *Context) {})
performRequest(router, "GET", "/logged") PerformRequest(router, "GET", "/logged")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
buffer.Reset() buffer.Reset()
performRequest(router, "GET", "/skipped") PerformRequest(router, "GET", "/skipped")
assert.Contains(t, buffer.String(), "") assert.Contains(t, buffer.String(), "")
} }
@ -405,11 +406,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
router.GET("/logged", func(c *Context) {}) router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {}) router.GET("/skipped", func(c *Context) {})
performRequest(router, "GET", "/logged") PerformRequest(router, "GET", "/logged")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
buffer.Reset() buffer.Reset()
performRequest(router, "GET", "/skipped") PerformRequest(router, "GET", "/skipped")
assert.Contains(t, buffer.String(), "") assert.Contains(t, buffer.String(), "")
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
signature += " XX " signature += " XX "
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
signature += " X " signature += " X "
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
signature += " XX " signature += " XX "
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
func TestMiddlewareNoMethodDisabled(t *testing.T) { func TestMiddlewareNoMethodDisabled(t *testing.T) {
signature := "" signature := ""
router := New() router := New()
// NoMethod disabled
router.HandleMethodNotAllowed = false router.HandleMethodNotAllowed = false
router.Use(func(c *Context) { router.Use(func(c *Context) {
signature += "A" signature += "A"
c.Next() c.Next()
@ -144,8 +147,9 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
router.POST("/", func(c *Context) { router.POST("/", func(c *Context) {
signature += " XX " signature += " XX "
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
@ -171,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
@ -186,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
c.Next() c.Next()
c.AbortWithStatus(http.StatusGone) c.AbortWithStatus(http.StatusGone)
signature += "B" signature += "B"
}) })
router.GET("/", func(c *Context) { router.GET("/", func(c *Context) {
signature += "C" signature += "C"
c.Next() c.Next()
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusGone, w.Code) assert.Equal(t, http.StatusGone, w.Code)
@ -216,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
signature += "C" signature += "C"
}) })
// RUN // RUN
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
@ -243,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) {
}) })
}) })
w := performRequest(router, "GET", "/") w := PerformRequest(router, "GET", "/")
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))

19
mode.go
View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package gin package gin
import ( import (
"flag"
"io" "io"
"os" "os"
@ -41,8 +42,10 @@ var DefaultWriter io.Writer = os.Stdout
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors // DefaultErrorWriter is the default io.Writer used by Gin to debug errors
var DefaultErrorWriter io.Writer = os.Stderr var DefaultErrorWriter io.Writer = os.Stderr
var ginMode = debugCode var (
var modeName = DebugMode ginMode = debugCode
modeName = DebugMode
)
func init() { func init() {
mode := os.Getenv(EnvGinMode) mode := os.Getenv(EnvGinMode)
@ -52,7 +55,11 @@ func init() {
// SetMode sets gin mode according to input string. // SetMode sets gin mode according to input string.
func SetMode(value string) { func SetMode(value string) {
if value == "" { if value == "" {
value = DebugMode if flag.Lookup("test.v") != nil {
value = TestMode
} else {
value = DebugMode
}
} }
switch value { switch value {
@ -63,7 +70,7 @@ func SetMode(value string) {
case TestMode: case TestMode:
ginMode = testCode ginMode = testCode
default: default:
panic("gin mode unknown: " + value) panic("gin mode unknown: " + value + " (available mode: debug release test)")
} }
modeName = value modeName = value
@ -86,7 +93,7 @@ func EnableJsonDecoderDisallowUnknownFields() {
binding.EnableDecoderDisallowUnknownFields = true binding.EnableDecoderDisallowUnknownFields = true
} }
// Mode returns currently gin mode. // Mode returns current gin mode.
func Mode() string { func Mode() string {
return modeName return modeName
} }

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package gin package gin
import ( import (
"flag"
"os" "os"
"testing" "testing"
@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
assert.Equal(t, TestMode, Mode()) assert.Equal(t, TestMode, Mode())
os.Unsetenv(EnvGinMode) os.Unsetenv(EnvGinMode)
SetMode("")
assert.Equal(t, testCode, ginMode)
assert.Equal(t, TestMode, Mode())
tmp := flag.CommandLine
flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
SetMode("") SetMode("")
assert.Equal(t, debugCode, ginMode) assert.Equal(t, debugCode, ginMode)
assert.Equal(t, DebugMode, Mode()) assert.Equal(t, DebugMode, Mode())
flag.CommandLine = tmp
SetMode(DebugMode) SetMode(DebugMode)
assert.Equal(t, debugCode, ginMode) assert.Equal(t, debugCode, ginMode)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,6 +6,7 @@ package gin
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -27,14 +28,14 @@ var (
) )
// RecoveryFunc defines the function passable to CustomRecovery. // RecoveryFunc defines the function passable to CustomRecovery.
type RecoveryFunc func(c *Context, err interface{}) type RecoveryFunc func(c *Context, err any)
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc { func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter) 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 { func CustomRecovery(handle RecoveryFunc) HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter, handle) return RecoveryWithWriter(DefaultErrorWriter, handle)
} }
@ -60,7 +61,8 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
// condition that warrants a panic stack trace. // condition that warrants a panic stack trace.
var brokenPipe bool var brokenPipe bool
if ne, ok := err.(*net.OpError); ok { if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok { var se *os.SyscallError
if errors.As(ne, &se) {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true brokenPipe = true
} }
@ -100,7 +102,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
} }
} }
func defaultHandleRecovery(c *Context, err interface{}) { func defaultHandleRecovery(c *Context, err any) {
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)
} }
@ -153,7 +155,7 @@ func function(pc uintptr) []byte {
// runtime/debug.*T·ptrmethod // runtime/debug.*T·ptrmethod
// and want // and want
// *T.ptrmethod // *T.ptrmethod
// Also the package path might contains dot (e.g. code.google.com/...), // Also the package path might contain dot (e.g. code.google.com/...),
// so first eliminate the path prefix // so first eliminate the path prefix
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
name = name[lastSlash+1:] name = name[lastSlash+1:]
@ -165,7 +167,7 @@ func function(pc uintptr) []byte {
return name return name
} }
// timeFormat returns a customized time string for logger.
func timeFormat(t time.Time) string { func timeFormat(t time.Time) string {
timeString := t.Format("2006/01/02 - 15:04:05") return t.Format("2006/01/02 - 15:04:05")
return timeString
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -27,7 +27,7 @@ func TestPanicClean(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery", w := PerformRequest(router, "GET", "/recovery",
header{ header{
Key: "Host", Key: "Host",
Value: "www.google.com", Value: "www.google.com",
@ -57,7 +57,7 @@ func TestPanicInHandler(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "panic recovered")
@ -68,7 +68,7 @@ func TestPanicInHandler(t *testing.T) {
// Debug mode prints the request // Debug mode prints the request
SetMode(DebugMode) SetMode(DebugMode)
// RUN // RUN
w = performRequest(router, "GET", "/recovery") w = PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")
@ -85,21 +85,21 @@ func TestPanicWithAbort(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
} }
func TestSource(t *testing.T) { func TestSource(t *testing.T) {
bs := source(nil, 0) bs := source(nil, 0)
assert.Equal(t, []byte("???"), bs) assert.Equal(t, dunno, bs)
in := [][]byte{ in := [][]byte{
[]byte("Hello world."), []byte("Hello world."),
[]byte("Hi, gin.."), []byte("Hi, gin.."),
} }
bs = source(in, 10) bs = source(in, 10)
assert.Equal(t, []byte("???"), bs) assert.Equal(t, dunno, bs)
bs = source(in, 1) bs = source(in, 1)
assert.Equal(t, []byte("Hello world."), bs) assert.Equal(t, []byte("Hello world."), bs)
@ -107,7 +107,7 @@ func TestSource(t *testing.T) {
func TestFunction(t *testing.T) { func TestFunction(t *testing.T) {
bs := function(1) bs := function(1)
assert.Equal(t, []byte("???"), bs) assert.Equal(t, dunno, bs)
} }
// TestPanicWithBrokenPipe asserts that recovery specifically handles // TestPanicWithBrokenPipe asserts that recovery specifically handles
@ -122,7 +122,6 @@ func TestPanicWithBrokenPipe(t *testing.T) {
for errno, expectMsg := range expectMsgs { for errno, expectMsg := range expectMsgs {
t.Run(expectMsg, func(t *testing.T) { t.Run(expectMsg, func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
router := New() router := New()
@ -137,7 +136,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
panic(e) panic(e)
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, expectCode, w.Code) assert.Equal(t, expectCode, w.Code)
assert.Contains(t, strings.ToLower(buf.String()), expectMsg) assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
@ -149,7 +148,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
errBuffer := new(bytes.Buffer) errBuffer := new(bytes.Buffer)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
router := New() router := New()
handleRecovery := func(c *Context, err interface{}) { handleRecovery := func(c *Context, err any) {
errBuffer.WriteString(err.(string)) errBuffer.WriteString(err.(string))
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
} }
@ -158,7 +157,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "panic recovered")
@ -169,7 +168,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
// Debug mode prints the request // Debug mode prints the request
SetMode(DebugMode) SetMode(DebugMode)
// RUN // RUN
w = performRequest(router, "GET", "/recovery") w = PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")
@ -184,7 +183,7 @@ func TestCustomRecovery(t *testing.T) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
router := New() router := New()
DefaultErrorWriter = buffer DefaultErrorWriter = buffer
handleRecovery := func(c *Context, err interface{}) { handleRecovery := func(c *Context, err any) {
errBuffer.WriteString(err.(string)) errBuffer.WriteString(err.(string))
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
} }
@ -193,7 +192,7 @@ func TestCustomRecovery(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "panic recovered")
@ -204,7 +203,7 @@ func TestCustomRecovery(t *testing.T) {
// Debug mode prints the request // Debug mode prints the request
SetMode(DebugMode) SetMode(DebugMode)
// RUN // RUN
w = performRequest(router, "GET", "/recovery") w = PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")
@ -219,7 +218,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
router := New() router := New()
DefaultErrorWriter = buffer DefaultErrorWriter = buffer
handleRecovery := func(c *Context, err interface{}) { handleRecovery := func(c *Context, err any) {
errBuffer.WriteString(err.(string)) errBuffer.WriteString(err.(string))
c.AbortWithStatus(http.StatusBadRequest) c.AbortWithStatus(http.StatusBadRequest)
} }
@ -228,7 +227,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "panic recovered")
@ -239,7 +238,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
// Debug mode prints the request // Debug mode prints the request
SetMode(DebugMode) SetMode(DebugMode)
// RUN // RUN
w = performRequest(router, "GET", "/recovery") w = PerformRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")

10
render/any.go Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2021 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package render
type any = interface{}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -20,7 +20,7 @@ type Delims struct {
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
type HTMLRender interface { type HTMLRender interface {
// Instance returns an HTML instance. // Instance returns an HTML instance.
Instance(string, interface{}) Render Instance(string, any) Render
} }
// HTMLProduction contains template reference and its delims. // HTMLProduction contains template reference and its delims.
@ -41,13 +41,13 @@ type HTMLDebug struct {
type HTML struct { type HTML struct {
Template *template.Template Template *template.Template
Name string Name string
Data interface{} Data any
} }
var htmlContentType = []string{"text/html; charset=utf-8"} var htmlContentType = []string{"text/html; charset=utf-8"}
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
func (r HTMLProduction) Instance(name string, data interface{}) Render { func (r HTMLProduction) Instance(name string, data any) Render {
return HTML{ return HTML{
Template: r.Template, Template: r.Template,
Name: name, Name: name,
@ -56,7 +56,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
} }
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. // Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
func (r HTMLDebug) Instance(name string, data interface{}) Render { func (r HTMLDebug) Instance(name string, data any) Render {
return HTML{ return HTML{
Template: r.loadTemplate(), Template: r.loadTemplate(),
Name: name, Name: name,

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -16,39 +16,41 @@ import (
// JSON contains the given interface object. // JSON contains the given interface object.
type JSON struct { type JSON struct {
Data interface{} Data any
} }
// IndentedJSON contains the given interface object. // IndentedJSON contains the given interface object.
type IndentedJSON struct { type IndentedJSON struct {
Data interface{} Data any
} }
// SecureJSON contains the given interface object and its prefix. // SecureJSON contains the given interface object and its prefix.
type SecureJSON struct { type SecureJSON struct {
Prefix string Prefix string
Data interface{} Data any
} }
// JsonpJSON contains the given interface object its callback. // JsonpJSON contains the given interface object its callback.
type JsonpJSON struct { type JsonpJSON struct {
Callback string Callback string
Data interface{} Data any
} }
// AsciiJSON contains the given interface object. // AsciiJSON contains the given interface object.
type AsciiJSON struct { type AsciiJSON struct {
Data interface{} Data any
} }
// PureJSON contains the given interface object. // PureJSON contains the given interface object.
type PureJSON struct { type PureJSON struct {
Data interface{} Data any
} }
var jsonContentType = []string{"application/json; charset=utf-8"} var (
var jsonpContentType = []string{"application/javascript; charset=utf-8"} jsonContentType = []string{"application/json; charset=utf-8"}
var jsonAsciiContentType = []string{"application/json"} jsonpContentType = []string{"application/javascript; charset=utf-8"}
jsonASCIIContentType = []string{"application/json"}
)
// Render (JSON) writes data with custom ContentType. // Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) { func (r JSON) Render(w http.ResponseWriter) (err error) {
@ -64,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
} }
// WriteJSON marshals the given interface object and writes it with custom ContentType. // WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error { func WriteJSON(w http.ResponseWriter, obj any) error {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj) jsonBytes, err := json.Marshal(obj)
if err != nil { if err != nil {
@ -100,8 +102,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
// if the jsonBytes is array values // if the jsonBytes is array values
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes, if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
bytesconv.StringToBytes("]")) { bytesconv.StringToBytes("]")) {
_, err = w.Write(bytesconv.StringToBytes(r.Prefix)) if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
if err != nil {
return err return err
} }
} }
@ -128,20 +129,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
} }
callback := template.JSEscapeString(r.Callback) callback := template.JSEscapeString(r.Callback)
_, err = w.Write(bytesconv.StringToBytes(callback)) if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
if err != nil {
return err return err
} }
_, err = w.Write(bytesconv.StringToBytes("("))
if err != nil { if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
return err return err
} }
_, err = w.Write(ret)
if err != nil { if _, err = w.Write(ret); err != nil {
return err return err
} }
_, err = w.Write(bytesconv.StringToBytes(");"))
if err != nil { if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
return err return err
} }
@ -176,7 +176,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
// WriteContentType (AsciiJSON) writes JSON ContentType. // WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonAsciiContentType) writeContentType(w, jsonASCIIContentType)
} }
// Render (PureJSON) writes custom ContentType and encodes the given interface object. // Render (PureJSON) writes custom ContentType and encodes the given interface object.

View File

@ -1,7 +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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package render package render
@ -12,13 +13,15 @@ import (
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
) )
// Check interface implemented here to support go build tag nomsgpack.
// See: https://github.com/gin-gonic/gin/pull/1852/
var ( var (
_ Render = MsgPack{} _ Render = MsgPack{}
) )
// MsgPack contains the given interface object. // MsgPack contains the given interface object.
type MsgPack struct { type MsgPack struct {
Data interface{} Data any
} }
var msgpackContentType = []string{"application/msgpack; charset=utf-8"} var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
@ -34,7 +37,7 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
} }
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object. // WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { func WriteMsgPack(w http.ResponseWriter, obj any) error {
writeContentType(w, msgpackContentType) writeContentType(w, msgpackContentType)
var mh codec.MsgpackHandle var mh codec.MsgpackHandle
return codec.NewEncoder(w, &mh).Encode(obj) return codec.NewEncoder(w, &mh).Encode(obj)

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -7,12 +7,12 @@ package render
import ( import (
"net/http" "net/http"
"github.com/golang/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// ProtoBuf contains the given interface object. // ProtoBuf contains the given interface object.
type ProtoBuf struct { type ProtoBuf struct {
Data interface{} Data any
} }
var protobufContentType = []string{"application/x-protobuf"} var protobufContentType = []string{"application/x-protobuf"}

View File

@ -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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -30,6 +30,7 @@ var (
_ Render = Reader{} _ Render = Reader{}
_ Render = AsciiJSON{} _ Render = AsciiJSON{}
_ Render = ProtoBuf{} _ Render = ProtoBuf{}
_ Render = TOML{}
) )
func writeContentType(w http.ResponseWriter, value []string) { func writeContentType(w http.ResponseWriter, value []string) {

View File

@ -1,7 +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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !nomsgpack
// +build !nomsgpack // +build !nomsgpack
package render package render
@ -20,7 +21,7 @@ import (
func TestRenderMsgPack(t *testing.T) { func TestRenderMsgPack(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
} }
@ -38,6 +39,6 @@ func TestRenderMsgPack(t *testing.T) {
err = codec.NewEncoder(buf, h).Encode(data) err = codec.NewEncoder(buf, h).Encode(data)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, w.Body.String(), string(buf.Bytes())) assert.Equal(t, w.Body.String(), buf.String())
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -14,10 +14,9 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
testdata "github.com/gin-gonic/gin/testdata/protoexample" testdata "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
) )
// TODO unit tests // TODO unit tests
@ -25,7 +24,7 @@ import (
func TestRenderJSON(t *testing.T) { func TestRenderJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
"html": "<b>", "html": "<b>",
} }
@ -50,7 +49,7 @@ func TestRenderJSONPanics(t *testing.T) {
func TestRenderIndentedJSON(t *testing.T) { func TestRenderIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
"bar": "foo", "bar": "foo",
} }
@ -73,7 +72,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
func TestRenderSecureJSON(t *testing.T) { func TestRenderSecureJSON(t *testing.T) {
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
} }
@ -87,7 +86,7 @@ func TestRenderSecureJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
datas := []map[string]interface{}{{ datas := []map[string]any{{
"foo": "bar", "foo": "bar",
}, { }, {
"bar": "foo", "bar": "foo",
@ -110,7 +109,7 @@ func TestRenderSecureJSONFail(t *testing.T) {
func TestRenderJsonpJSON(t *testing.T) { func TestRenderJsonpJSON(t *testing.T) {
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
} }
@ -124,7 +123,7 @@ func TestRenderJsonpJSON(t *testing.T) {
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
datas := []map[string]interface{}{{ datas := []map[string]any{{
"foo": "bar", "foo": "bar",
}, { }, {
"bar": "foo", "bar": "foo",
@ -138,7 +137,7 @@ func TestRenderJsonpJSON(t *testing.T) {
func TestRenderJsonpJSONError2(t *testing.T) { func TestRenderJsonpJSONError2(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
} }
(JsonpJSON{"", data}).WriteContentType(w) (JsonpJSON{"", data}).WriteContentType(w)
@ -162,7 +161,7 @@ func TestRenderJsonpJSONFail(t *testing.T) {
func TestRenderAsciiJSON(t *testing.T) { func TestRenderAsciiJSON(t *testing.T) {
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
data1 := map[string]interface{}{ data1 := map[string]any{
"lang": "GO语言", "lang": "GO语言",
"tag": "<br>", "tag": "<br>",
} }
@ -191,7 +190,7 @@ func TestRenderAsciiJSONFail(t *testing.T) {
func TestRenderPureJSON(t *testing.T) { func TestRenderPureJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]any{
"foo": "bar", "foo": "bar",
"html": "<b>", "html": "<b>",
} }
@ -201,7 +200,7 @@ func TestRenderPureJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }
type xmlmap map[string]interface{} type xmlmap map[string]any
// Allows type H to be used with xml.Marshal // Allows type H to be used with xml.Marshal
func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
@ -245,7 +244,7 @@ b:
type fail struct{} type fail struct{}
// Hook MarshalYAML // Hook MarshalYAML
func (ft *fail) MarshalYAML() (interface{}, error) { func (ft *fail) MarshalYAML() (any, error) {
return nil, errors.New("fail") return nil, errors.New("fail")
} }
@ -359,13 +358,13 @@ func TestRenderString(t *testing.T) {
(String{ (String{
Format: "hello %s %d", Format: "hello %s %d",
Data: []interface{}{}, Data: []any{},
}).WriteContentType(w) }).WriteContentType(w)
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
err := (String{ err := (String{
Format: "hola %s %d", Format: "hola %s %d",
Data: []interface{}{"manu", 2}, Data: []any{"manu", 2},
}).Render(w) }).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -378,7 +377,7 @@ func TestRenderStringLenZero(t *testing.T) {
err := (String{ err := (String{
Format: "hola %s %d", Format: "hola %s %d",
Data: []interface{}{}, Data: []any{},
}).Render(w) }).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -404,7 +403,7 @@ func TestRenderHTMLTemplate(t *testing.T) {
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
htmlRender := HTMLProduction{Template: templ} htmlRender := HTMLProduction{Template: templ}
instance := htmlRender.Instance("t", map[string]interface{}{ instance := htmlRender.Instance("t", map[string]any{
"name": "alexandernyquist", "name": "alexandernyquist",
}) })
@ -420,7 +419,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
templ := template.Must(template.New("").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
htmlRender := HTMLProduction{Template: templ} htmlRender := HTMLProduction{Template: templ}
instance := htmlRender.Instance("", map[string]interface{}{ instance := htmlRender.Instance("", map[string]any{
"name": "alexandernyquist", "name": "alexandernyquist",
}) })
@ -433,12 +432,13 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, htmlRender := HTMLDebug{
Files: []string{"../testdata/template/hello.tmpl"},
Glob: "", Glob: "",
Delims: Delims{Left: "{[{", Right: "}]}"}, Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil, FuncMap: nil,
} }
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ instance := htmlRender.Instance("hello.tmpl", map[string]any{
"name": "thinkerou", "name": "thinkerou",
}) })
@ -451,12 +451,13 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
func TestRenderHTMLDebugGlob(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: nil, htmlRender := HTMLDebug{
Files: nil,
Glob: "../testdata/template/hello*", Glob: "../testdata/template/hello*",
Delims: Delims{Left: "{[{", Right: "}]}"}, Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil, FuncMap: nil,
} }
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ instance := htmlRender.Instance("hello.tmpl", map[string]any{
"name": "thinkerou", "name": "thinkerou",
}) })
@ -468,7 +469,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
} }
func TestRenderHTMLDebugPanics(t *testing.T) { func TestRenderHTMLDebugPanics(t *testing.T) {
htmlRender := HTMLDebug{Files: nil, htmlRender := HTMLDebug{
Files: nil,
Glob: "", Glob: "",
Delims: Delims{"{{", "}}"}, Delims: Delims{"{{", "}}"},
FuncMap: nil, FuncMap: nil,

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -14,7 +14,7 @@ import (
// String contains the given interface object slice and its format. // String contains the given interface object slice and its format.
type String struct { type String struct {
Format string Format string
Data []interface{} Data []any
} }
var plainContentType = []string{"text/plain; charset=utf-8"} var plainContentType = []string{"text/plain; charset=utf-8"}
@ -30,7 +30,7 @@ func (r String) WriteContentType(w http.ResponseWriter) {
} }
// WriteString writes data according to its format and write custom ContentType. // WriteString writes data according to its format and write custom ContentType.
func WriteString(w http.ResponseWriter, format string, data []interface{}, html bool) (err error) { func WriteString(w http.ResponseWriter, format string, data []any, html bool) (err error) {
if html { if html {
writeContentType(w, htmlContentType) writeContentType(w, htmlContentType)
} else { } else {

36
render/toml.go Normal file
View 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)
}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,7 +11,7 @@ import (
// XML contains the given interface object. // XML contains the given interface object.
type XML struct { type XML struct {
Data interface{} Data any
} }
var xmlContentType = []string{"application/xml; charset=utf-8"} var xmlContentType = []string{"application/xml; charset=utf-8"}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,7 +12,7 @@ import (
// YAML contains the given interface object. // YAML contains the given interface object.
type YAML struct { type YAML struct {
Data interface{} Data any
} }
var yamlContentType = []string{"application/x-yaml; charset=utf-8"} var yamlContentType = []string{"application/x-yaml; charset=utf-8"}

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -23,23 +23,23 @@ type ResponseWriter interface {
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
// Returns the HTTP response status code of the current request. // Status returns the HTTP response status code of the current request.
Status() int Status() int
// Returns the number of bytes already written into the response http body. // Size returns the number of bytes already written into the response http body.
// See Written() // See Written()
Size() int Size() int
// Writes the string into the response body. // WriteString writes the string into the response body.
WriteString(string) (int, error) WriteString(string) (int, error)
// Returns true if the response body was already written. // Written returns true if the response body was already written.
Written() bool Written() bool
// Forces to write the http header (status code + headers). // WriteHeaderNow forces to write the http header (status code + headers).
WriteHeaderNow() WriteHeaderNow()
// get the http.Pusher for server push // Pusher get the http.Pusher for server push
Pusher() http.Pusher Pusher() http.Pusher
} }
@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack() return w.ResponseWriter.(http.Hijacker).Hijack()
} }
// CloseNotify implements the http.CloseNotify interface. // CloseNotify implements the http.CloseNotifier interface.
func (w *responseWriter) CloseNotify() <-chan bool { func (w *responseWriter) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify() return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
// Flush implements the http.Flush interface. // Flush implements the http.Flusher interface.
func (w *responseWriter) Flush() { func (w *responseWriter) Flush() {
w.WriteHeaderNow() w.WriteHeaderNow()
w.ResponseWriter.(http.Flusher).Flush() w.ResponseWriter.(http.Flusher).Flush()

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -17,12 +17,14 @@ import (
// func (w *responseWriter) CloseNotify() <-chan bool { // func (w *responseWriter) CloseNotify() <-chan bool {
// func (w *responseWriter) Flush() { // func (w *responseWriter) Flush() {
var _ ResponseWriter = &responseWriter{} var (
var _ http.ResponseWriter = &responseWriter{} _ ResponseWriter = &responseWriter{}
var _ http.ResponseWriter = ResponseWriter(&responseWriter{}) _ http.ResponseWriter = &responseWriter{}
var _ http.Hijacker = ResponseWriter(&responseWriter{}) _ http.ResponseWriter = ResponseWriter(&responseWriter{})
var _ http.Flusher = ResponseWriter(&responseWriter{}) _ http.Hijacker = ResponseWriter(&responseWriter{})
var _ http.CloseNotifier = ResponseWriter(&responseWriter{}) _ http.Flusher = ResponseWriter(&responseWriter{})
_ http.CloseNotifier = ResponseWriter(&responseWriter{})
)
func init() { func init() {
SetMode(TestMode) SetMode(TestMode)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,6 +11,18 @@ import (
"strings" "strings"
) )
var (
// regEnLetter matches english letters for http method name
regEnLetter = regexp.MustCompile("^[A-Z]+$")
// anyMethods for RouterGroup Any method
anyMethods = []string{
http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
http.MethodTrace,
}
)
// IRouter defines all router handle interface includes single and group router. // IRouter defines all router handle interface includes single and group router.
type IRouter interface { type IRouter interface {
IRoutes IRoutes
@ -32,6 +44,7 @@ type IRoutes interface {
HEAD(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes StaticFile(string, string) IRoutes
StaticFileFS(string, string, http.FileSystem) IRoutes
Static(string, string) IRoutes Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes StaticFS(string, http.FileSystem) IRoutes
} }
@ -87,7 +100,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
// frequently used, non-standardized or custom methods (e.g. for internal // frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy). // communication with a proxy).
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil { if matched := regEnLetter.MatchString(httpMethod); !matched {
panic("http method " + httpMethod + " is not valid") panic("http method " + httpMethod + " is not valid")
} }
return group.handle(httpMethod, relativePath, handlers) return group.handle(httpMethod, relativePath, handlers)
@ -131,27 +144,34 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo
// Any registers a route that matches all the HTTP methods. // Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
group.handle(http.MethodGet, relativePath, handlers) for _, method := range anyMethods {
group.handle(http.MethodPost, relativePath, handlers) group.handle(method, relativePath, handlers)
group.handle(http.MethodPut, relativePath, handlers) }
group.handle(http.MethodPatch, relativePath, handlers)
group.handle(http.MethodHead, relativePath, handlers)
group.handle(http.MethodOptions, relativePath, handlers)
group.handle(http.MethodDelete, relativePath, handlers)
group.handle(http.MethodConnect, relativePath, handlers)
group.handle(http.MethodTrace, relativePath, handlers)
return group.returnObj() return group.returnObj()
} }
// StaticFile registers a single route in order to serve a single file of the local filesystem. // StaticFile registers a single route in order to serve a single file of the local filesystem.
// router.StaticFile("favicon.ico", "./resources/favicon.ico") // router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
return group.staticFileHandler(relativePath, func(c *Context) {
c.File(filepath)
})
}
// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
// Gin by default user: gin.Dir()
func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
return group.staticFileHandler(relativePath, func(c *Context) {
c.FileFromFS(filepath, fs)
})
}
func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
panic("URL parameters can not be used when serving a static file") panic("URL parameters can not be used when serving a static file")
} }
handler := func(c *Context) {
c.File(filepath)
}
group.GET(relativePath, handler) group.GET(relativePath, handler)
group.HEAD(relativePath, handler) group.HEAD(relativePath, handler)
return group.returnObj() return group.returnObj()
@ -209,9 +229,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers) finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) { assert1(finalSize < int(abortIndex), "too many handlers")
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize) mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers) copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers) copy(mergedHandlers[len(group.Handlers):], handlers)

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) {
panic("unknown method") panic("unknown method")
} }
w := performRequest(router, method, "/v1/login/test") w := PerformRequest(router, method, "/v1/login/test")
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
w = performRequest(router, method, "/v1/test") w = PerformRequest(router, method, "/v1/test")
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
} }
@ -111,16 +111,31 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) {
}) })
} }
func TestRouterGroupTooManyHandlers(t *testing.T) { func TestRouterGroupInvalidStaticFileFS(t *testing.T) {
router := New() router := New()
handlers1 := make([]HandlerFunc, 40) assert.Panics(t, func() {
router.StaticFileFS("/path/:param", "favicon.ico", Dir(".", false))
})
assert.Panics(t, func() {
router.StaticFileFS("/path/*param", "favicon.ico", Dir(".", false))
})
}
func TestRouterGroupTooManyHandlers(t *testing.T) {
const (
panicValue = "too many handlers"
maximumCnt = abortIndex
)
router := New()
handlers1 := make([]HandlerFunc, maximumCnt-1)
router.Use(handlers1...) router.Use(handlers1...)
handlers2 := make([]HandlerFunc, 26) handlers2 := make([]HandlerFunc, maximumCnt+1)
assert.Panics(t, func() { assert.PanicsWithValue(t, panicValue, func() {
router.Use(handlers2...) router.Use(handlers2...)
}) })
assert.Panics(t, func() { assert.PanicsWithValue(t, panicValue, func() {
router.GET("/", handlers2...) router.GET("/", handlers2...)
}) })
} }
@ -173,6 +188,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
assert.Equal(t, r, r.HEAD("/", handler)) assert.Equal(t, r, r.HEAD("/", handler))
assert.Equal(t, r, r.StaticFile("/file", ".")) assert.Equal(t, r, r.StaticFile("/file", "."))
assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false)))
assert.Equal(t, r, r.Static("/static", ".")) assert.Equal(t, r, r.Static("/static", "."))
assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false))) assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -21,7 +21,8 @@ type header struct {
Value string Value string
} }
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { // PerformRequest for testing gin router.
func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil) req := httptest.NewRequest(method, path, nil)
for _, h := range headers { for _, h := range headers {
req.Header.Add(h.Key, h.Value) req.Header.Add(h.Key, h.Value)
@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) {
passed = true passed = true
}) })
w := performRequest(r, method, "/test") w := PerformRequest(r, method, "/test")
assert.True(t, passed) assert.True(t, passed)
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
performRequest(r, method, "/test2") PerformRequest(r, method, "/test2")
assert.True(t, passedAny) assert.True(t, passedAny)
} }
@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) {
passed = true passed = true
}) })
w := performRequest(router, method, "/test") w := PerformRequest(router, method, "/test")
assert.False(t, passed) assert.False(t, passed)
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) {
passed = true passed = true
}) })
w := performRequest(router, method, "/test") w := PerformRequest(router, method, "/test")
assert.False(t, passed) assert.False(t, passed)
assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) {
c.String(http.StatusOK, "sup3") c.String(http.StatusOK, "sup3")
}) })
w := performRequest(router, http.MethodPut, "/hey") w := PerformRequest(router, http.MethodPut, "/hey")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "called", w.Body.String()) assert.Equal(t, "called", w.Body.String())
@ -150,50 +151,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
router.POST("/path3", func(c *Context) {}) router.POST("/path3", func(c *Context) {})
router.PUT("/path4/", func(c *Context) {}) router.PUT("/path4/", func(c *Context) {})
w := performRequest(router, http.MethodGet, "/path/") w := PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, http.MethodGet, "/path2") w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, http.MethodPost, "/path3/") w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, "/path3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, http.MethodPut, "/path4") w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, "/path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, http.MethodGet, "/path") w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, http.MethodGet, "/path2/") w = PerformRequest(router, http.MethodGet, "/path2/")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, http.MethodPost, "/path3") w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, http.MethodPut, "/path4/") w = PerformRequest(router, http.MethodPut, "/path4/")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
assert.Equal(t, "/api/path2/", w.Header().Get("Location")) assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code) assert.Equal(t, 301, w.Code)
w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
assert.Equal(t, 200, w.Code) assert.Equal(t, 200, w.Code)
router.RedirectTrailingSlash = false router.RedirectTrailingSlash = false
w = performRequest(router, http.MethodGet, "/path/") w = PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, http.MethodGet, "/path2") w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, http.MethodPost, "/path3/") w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, http.MethodPut, "/path4") w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
@ -207,19 +208,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
router.POST("/PATH3", func(c *Context) {}) router.POST("/PATH3", func(c *Context) {})
router.POST("/Path4/", func(c *Context) {}) router.POST("/Path4/", func(c *Context) {})
w := performRequest(router, http.MethodGet, "/PATH") w := PerformRequest(router, http.MethodGet, "/PATH")
assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, http.MethodGet, "/path2") w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, "/Path2", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, http.MethodPost, "/path3") w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, "/PATH3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, http.MethodPost, "/path4") w = PerformRequest(router, http.MethodPost, "/path4")
assert.Equal(t, "/Path4/", w.Header().Get("Location")) assert.Equal(t, "/Path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
} }
@ -238,7 +239,6 @@ func TestRouteParamsByName(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, name, c.Param("name")) assert.Equal(t, name, c.Param("name"))
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name")) assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf")) assert.Empty(t, c.Param("wtf"))
@ -249,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) {
assert.False(t, ok) assert.False(t, ok)
}) })
w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great") w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name) assert.Equal(t, "john", name)
@ -272,7 +272,6 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, name, c.Param("name")) assert.Equal(t, name, c.Param("name"))
assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name")) assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf")) assert.Empty(t, c.Param("wtf"))
@ -283,7 +282,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
assert.False(t, ok) assert.False(t, ok)
}) })
w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great") w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name) assert.Equal(t, "john", name)
@ -311,16 +310,50 @@ func TestRouteStaticFile(t *testing.T) {
router.Static("/using_static", dir) router.Static("/using_static", dir)
router.StaticFile("/result", f.Name()) router.StaticFile("/result", f.Name())
w := performRequest(router, http.MethodGet, "/using_static/"+filename) w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
w2 := performRequest(router, http.MethodGet, "/result") w2 := PerformRequest(router, http.MethodGet, "/result")
assert.Equal(t, w, w2) assert.Equal(t, w, w2)
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
w4 := performRequest(router, http.MethodHead, "/result") w4 := PerformRequest(router, http.MethodHead, "/result")
assert.Equal(t, w3, w4)
assert.Equal(t, http.StatusOK, w3.Code)
}
// TestHandleStaticFile - ensure the static file handles properly
func TestRouteStaticFileFS(t *testing.T) {
// SETUP file
testRoot, _ := os.Getwd()
f, err := ioutil.TempFile(testRoot, "")
if err != nil {
t.Error(err)
}
defer os.Remove(f.Name())
_, err = f.WriteString("Gin Web Framework")
assert.NoError(t, err)
f.Close()
dir, filename := filepath.Split(f.Name())
// SETUP gin
router := New()
router.Static("/using_static", dir)
router.StaticFileFS("/result_fs", filename, Dir(dir, false))
w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
w2 := PerformRequest(router, http.MethodGet, "/result_fs")
assert.Equal(t, w, w2)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
w4 := PerformRequest(router, http.MethodHead, "/result_fs")
assert.Equal(t, w3, w4) assert.Equal(t, w3, w4)
assert.Equal(t, http.StatusOK, w3.Code) assert.Equal(t, http.StatusOK, w3.Code)
@ -331,7 +364,7 @@ func TestRouteStaticListingDir(t *testing.T) {
router := New() router := New()
router.StaticFS("/", Dir("./", true)) router.StaticFS("/", Dir("./", true))
w := performRequest(router, http.MethodGet, "/") w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "gin.go") assert.Contains(t, w.Body.String(), "gin.go")
@ -343,7 +376,7 @@ func TestRouteStaticNoListing(t *testing.T) {
router := New() router := New()
router.Static("/", "./") router.Static("/", "./")
w := performRequest(router, http.MethodGet, "/") w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go") assert.NotContains(t, w.Body.String(), "gin.go")
@ -358,11 +391,13 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
}) })
static.Static("/", "./") static.Static("/", "./")
w := performRequest(router, http.MethodGet, "/gin.go") w := PerformRequest(router, http.MethodGet, "/gin.go")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "package gin") assert.Contains(t, w.Body.String(), "package gin")
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) // Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
// else, Content-Type='text/x-go; charset=utf-8'
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
@ -372,13 +407,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
router := New() router := New()
router.HandleMethodNotAllowed = true router.HandleMethodNotAllowed = true
router.POST("/path", func(c *Context) {}) router.POST("/path", func(c *Context) {})
w := performRequest(router, http.MethodGet, "/path") w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
router.NoMethod(func(c *Context) { router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText") c.String(http.StatusTeapot, "responseText")
}) })
w = performRequest(router, http.MethodGet, "/path") w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "responseText", w.Body.String()) assert.Equal(t, "responseText", w.Body.String())
assert.Equal(t, http.StatusTeapot, w.Code) assert.Equal(t, http.StatusTeapot, w.Code)
} }
@ -389,7 +424,7 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
// add one methodTree to trees // add one methodTree to trees
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
router.GET("/path2", func(c *Context) {}) router.GET("/path2", func(c *Context) {})
w := performRequest(router, http.MethodPost, "/path2") w := PerformRequest(router, http.MethodPost, "/path2")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
} }
@ -397,13 +432,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
router := New() router := New()
router.HandleMethodNotAllowed = false router.HandleMethodNotAllowed = false
router.POST("/path", func(c *Context) {}) router.POST("/path", func(c *Context) {})
w := performRequest(router, http.MethodGet, "/path") w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
router.NoMethod(func(c *Context) { router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText") c.String(http.StatusTeapot, "responseText")
}) })
w = performRequest(router, http.MethodGet, "/path") w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, "404 page not found", w.Body.String())
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
@ -423,7 +458,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
{"/nope", http.StatusNotFound, ""}, // NotFound {"/nope", http.StatusNotFound, ""}, // NotFound
} }
for _, tr := range testRoutes { for _, tr := range testRoutes {
w := performRequest(router, "GET", tr.route) w := PerformRequest(router, "GET", tr.route)
assert.Equal(t, tr.code, w.Code) assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound { if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@ -453,7 +488,7 @@ func TestRouterNotFound(t *testing.T) {
{"/nope", http.StatusNotFound, ""}, // NotFound {"/nope", http.StatusNotFound, ""}, // NotFound
} }
for _, tr := range testRoutes { for _, tr := range testRoutes {
w := performRequest(router, http.MethodGet, tr.route) w := PerformRequest(router, http.MethodGet, tr.route)
assert.Equal(t, tr.code, w.Code) assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound { if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@ -466,21 +501,36 @@ func TestRouterNotFound(t *testing.T) {
c.AbortWithStatus(http.StatusNotFound) c.AbortWithStatus(http.StatusNotFound)
notFound = true notFound = true
}) })
w := performRequest(router, http.MethodGet, "/nope") w := PerformRequest(router, http.MethodGet, "/nope")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.True(t, notFound) assert.True(t, notFound)
// Test other method than GET (want 307 instead of 301) // Test other method than GET (want 307 instead of 301)
router.PATCH("/path", func(c *Context) {}) router.PATCH("/path", func(c *Context) {})
w = performRequest(router, http.MethodPatch, "/path/") w = PerformRequest(router, http.MethodPatch, "/path/")
assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
// Test special case where no node for the prefix "/" exists // Test special case where no node for the prefix "/" exists
router = New() router = New()
router.GET("/a", func(c *Context) {}) router.GET("/a", func(c *Context) {})
w = performRequest(router, http.MethodGet, "/") w = PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
// Reproduction test for the bug of issue #2843
router = New()
router.NoRoute(func(c *Context) {
if c.Request.RequestURI == "/login" {
c.String(200, "login")
}
})
router.GET("/logout", func(c *Context) {
c.String(200, "logout")
})
w = PerformRequest(router, http.MethodGet, "/login")
assert.Equal(t, "login", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/logout")
assert.Equal(t, "logout", w.Body.String())
} }
func TestRouterStaticFSNotFound(t *testing.T) { func TestRouterStaticFSNotFound(t *testing.T) {
@ -490,10 +540,10 @@ func TestRouterStaticFSNotFound(t *testing.T) {
c.String(404, "non existent") c.String(404, "non existent")
}) })
w := performRequest(router, http.MethodGet, "/nonexistent") w := PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String()) assert.Equal(t, "non existent", w.Body.String())
w = performRequest(router, http.MethodHead, "/nonexistent") w = PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String()) assert.Equal(t, "non existent", w.Body.String())
} }
@ -503,7 +553,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
router.StaticFS("/", http.FileSystem(http.Dir("."))) router.StaticFS("/", http.FileSystem(http.Dir(".")))
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
performRequest(router, http.MethodGet, "/nonexistent") PerformRequest(router, http.MethodGet, "/nonexistent")
}) })
} }
@ -520,11 +570,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
// First access // First access
performRequest(router, http.MethodGet, "/nonexistent") PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, 1, middlewareCalledNum) assert.Equal(t, 1, middlewareCalledNum)
// Second access // Second access
performRequest(router, http.MethodHead, "/nonexistent") PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, 2, middlewareCalledNum) assert.Equal(t, 2, middlewareCalledNum)
} }
@ -543,7 +593,7 @@ func TestRouteRawPath(t *testing.T) {
assert.Equal(t, "222", num) assert.Equal(t, "222", num)
}) })
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222") w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
@ -563,7 +613,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
assert.Equal(t, "333", num) assert.Equal(t, "333", num)
}) })
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333") w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
@ -574,7 +624,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
c.Next() c.Next()
}) })
w := performRequest(route, http.MethodGet, "/NotFound") w := PerformRequest(route, http.MethodGet, "/NotFound")
assert.Equal(t, 421, w.Code) assert.Equal(t, 421, w.Code)
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
@ -608,7 +658,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
} }
for _, route := range routes { for _, route := range routes {
w := performRequest(router, http.MethodGet, route) w := PerformRequest(router, http.MethodGet, route)
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
@ -618,6 +668,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
assert.Equal(t, "", c.FullPath()) assert.Equal(t, "", c.FullPath())
}) })
w := performRequest(router, http.MethodGet, "/not-found") w := PerformRequest(router, http.MethodGet, "/not-found")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }

View 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 // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

10
testdata/protoexample/any.go vendored Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2021 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package protoexample
type any = interface{}

View File

@ -1,24 +1,24 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.0
// protoc v3.15.8
// source: test.proto // source: test.proto
// DO NOT EDIT!
/*
Package protoexample is a generated protocol buffer package.
It is generated from these files:
test.proto
It has these top-level messages:
Test
*/
package protoexample package protoexample
import proto "github.com/golang/protobuf/proto" import (
import math "math" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used. const (
var _ = proto.Marshal // Verify that this generated code is sufficiently up-to-date.
var _ = math.Inf _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type FOO int32 type FOO int32
@ -26,88 +26,273 @@ const (
FOO_X FOO = 17 FOO_X FOO = 17
) )
var FOO_name = map[int32]string{ // Enum value maps for FOO.
17: "X", var (
} FOO_name = map[int32]string{
var FOO_value = map[string]int32{ 17: "X",
"X": 17, }
} FOO_value = map[string]int32{
"X": 17,
}
)
func (x FOO) Enum() *FOO { func (x FOO) Enum() *FOO {
p := new(FOO) p := new(FOO)
*p = x *p = x
return p return p
} }
func (x FOO) String() string { func (x FOO) String() string {
return proto.EnumName(FOO_name, int32(x)) return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
} }
func (x *FOO) UnmarshalJSON(data []byte) error {
value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") func (FOO) Descriptor() protoreflect.EnumDescriptor {
return file_test_proto_enumTypes[0].Descriptor()
}
func (FOO) Type() protoreflect.EnumType {
return &file_test_proto_enumTypes[0]
}
func (x FOO) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *FOO) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil { if err != nil {
return err return err
} }
*x = FOO(value) *x = FOO(num)
return nil return nil
} }
type Test struct { // Deprecated: Use FOO.Descriptor instead.
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` func (FOO) EnumDescriptor() ([]byte, []int) {
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` return file_test_proto_rawDescGZIP(), []int{0}
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
XXX_unrecognized []byte `json:"-"`
} }
func (m *Test) Reset() { *m = Test{} } type Test struct {
func (m *Test) String() string { return proto.CompactTextString(m) } state protoimpl.MessageState
func (*Test) ProtoMessage() {} sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
const Default_Test_Type int32 = 77 Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"`
}
func (m *Test) GetLabel() string { // Default values for Test fields.
if m != nil && m.Label != nil { const (
return *m.Label Default_Test_Type = int32(77)
)
func (x *Test) Reset() {
*x = Test{}
if protoimpl.UnsafeEnabled {
mi := &file_test_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Test) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Test) ProtoMessage() {}
func (x *Test) ProtoReflect() protoreflect.Message {
mi := &file_test_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Test.ProtoReflect.Descriptor instead.
func (*Test) Descriptor() ([]byte, []int) {
return file_test_proto_rawDescGZIP(), []int{0}
}
func (x *Test) GetLabel() string {
if x != nil && x.Label != nil {
return *x.Label
} }
return "" return ""
} }
func (m *Test) GetType() int32 { func (x *Test) GetType() int32 {
if m != nil && m.Type != nil { if x != nil && x.Type != nil {
return *m.Type return *x.Type
} }
return Default_Test_Type return Default_Test_Type
} }
func (m *Test) GetReps() []int64 { func (x *Test) GetReps() []int64 {
if m != nil { if x != nil {
return m.Reps return x.Reps
} }
return nil return nil
} }
func (m *Test) GetOptionalgroup() *Test_OptionalGroup { func (x *Test) GetOptionalgroup() *Test_OptionalGroup {
if m != nil { if x != nil {
return m.Optionalgroup return x.Optionalgroup
} }
return nil return nil
} }
type Test_OptionalGroup struct { type Test_OptionalGroup struct {
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` state protoimpl.MessageState
XXX_unrecognized []byte `json:"-"` sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequiredField *string `protobuf:"bytes,5,req,name=RequiredField" json:"RequiredField,omitempty"`
} }
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } func (x *Test_OptionalGroup) Reset() {
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } *x = Test_OptionalGroup{}
func (*Test_OptionalGroup) ProtoMessage() {} if protoimpl.UnsafeEnabled {
mi := &file_test_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (m *Test_OptionalGroup) GetRequiredField() string { func (x *Test_OptionalGroup) String() string {
if m != nil && m.RequiredField != nil { return protoimpl.X.MessageStringOf(x)
return *m.RequiredField }
func (*Test_OptionalGroup) ProtoMessage() {}
func (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {
mi := &file_test_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.
func (*Test_OptionalGroup) Descriptor() ([]byte, []int) {
return file_test_proto_rawDescGZIP(), []int{0, 0}
}
func (x *Test_OptionalGroup) GetRequiredField() string {
if x != nil && x.RequiredField != nil {
return *x.RequiredField
} }
return "" return ""
} }
func init() { var File_test_proto protoreflect.FileDescriptor
proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
var file_test_proto_rawDesc = []byte{
0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,
0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,
0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,
0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,
0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,
0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,
0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,
0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,
0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,
0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58,
0x10, 0x11,
}
var (
file_test_proto_rawDescOnce sync.Once
file_test_proto_rawDescData = file_test_proto_rawDesc
)
func file_test_proto_rawDescGZIP() []byte {
file_test_proto_rawDescOnce.Do(func() {
file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
})
return file_test_proto_rawDescData
}
var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_test_proto_goTypes = []any{
(FOO)(0), // 0: protoexample.FOO
(*Test)(nil), // 1: protoexample.Test
(*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup
}
var file_test_proto_depIdxs = []int32{
2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_test_proto_init() }
func file_test_proto_init() {
if File_test_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_test_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Test); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_test_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*Test_OptionalGroup); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_test_proto_rawDesc,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_test_proto_goTypes,
DependencyIndexes: file_test_proto_depIdxs,
EnumInfos: file_test_proto_enumTypes,
MessageInfos: file_test_proto_msgTypes,
}.Build()
File_test_proto = out.File
file_test_proto_rawDesc = nil
file_test_proto_goTypes = nil
file_test_proto_depIdxs = nil
} }

View File

@ -1 +1 @@
Date: {[{.now | formatAsDate}]} Date: {[{.now | formatAsDate}]}

Some files were not shown because too many files have changed in this diff Show More