Merge branch 'gin-gonic:master' into master

This commit is contained in:
Pavel Zarubin 2022-08-22 22:55:02 +03:00 committed by GitHub
commit b34e0714b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 2140 additions and 1312 deletions

View File

@ -37,7 +37,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -46,4 +46,4 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main # queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@ -13,23 +13,23 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup go - name: Setup go
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: '^1.16' go-version: '^1.16'
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup golangci-lint - name: Setup golangci-lint
uses: golangci/golangci-lint-action@v3.1.0 uses: golangci/golangci-lint-action@v3.2.0
with: with:
version: v1.45.0 version: v1.48.0
args: --verbose args: --verbose
test: test:
needs: lint needs: lint
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
go: [1.14, 1.15, 1.16, 1.17, 1.18] go: [1.15, 1.16, 1.17, 1.18]
test-tags: ['', nomsgpack] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json']
include: include:
- os: ubuntu-latest - os: ubuntu-latest
go-build: ~/.cache/go-build go-build: ~/.cache/go-build
@ -43,7 +43,7 @@ jobs:
GOPROXY: https://proxy.golang.org GOPROXY: https://proxy.golang.org
steps: steps:
- name: Set up Go ${{ matrix.go }} - name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
@ -65,9 +65,13 @@ jobs:
run: make test run: make test
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v3
with: with:
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
- name: Format
if: matrix.go-version == '1.19.x'
run: diff -u <(echo -n) <(gofmt -d .)
notification-gitter: notification-gitter:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest

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

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

@ -2,237 +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>
**@jincheng9 (Jincheng Zhang)** - Ildar1111 <54001462+Ildar1111@users.noreply.github.com>
- ★ support TSR when wildcard follows named param - Iskander (Alex) Sharipov <iskander.sharipov@intel.com>
- Fix errors and typos in comments - Ismail Gjevori <isgjevori@protonmail.com>
- Ivan Chen <allenivan@gmail.com>
- JINNOUCHI Yasushi <delphinus@remora.cx>
**@joiggama (Ignacio Galindo)** - James Pettyjohn <japettyjohn@users.noreply.github.com>
- Add utf-8 charset header on renders - Jamie Stackhouse <jamie.stackhouse@redspace.com>
- Jason Lee <jawc@hotmail.com>
- Javier Provecho <j.provecho@dartekstudios.com>
**@julienschmidt (Julien Schmidt)** - Javier Provecho <javier.provecho@bq.com>
- gofmt the code examples - Javier Provecho <javiertitan@gmail.com>
- Javier Provecho Fernandez <j.provecho@dartekstudios.com>
- Javier Provecho Fernandez <javiertitan@gmail.com>
**@kelcecil (Kel Cecil)** - Jean-Christophe Lebreton <jclebreton@gmail.com>
- Fix readme typo - Jeff <laojianzi1994@gmail.com>
- Jeremy Loy <jeremy.b.loy@icloud.com>
- Jim Filippou <p3160253@aueb.gr>
**@kyledinh (Kyle Dinh)** - Jimmy Pettersson <jimmy@expertmaker.com>
- Adds RunTLS() - John Bampton <jbampton@users.noreply.github.com>
- Johnny Dallas <johnnydallas0308@gmail.com>
- Johnny Dallas <theonlyjohnny@theonlyjohnny.sh>
**@LinusU (Linus Unnebäck)** - Jonathan (JC) Chen <jc@dijonkitchen.org>
- Small fixes in README - Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com>
- Josh Horowitz <joshua.m.horowitz@gmail.com>
- Joshua Loper <josh.el3@gmail.com>
**@loongmxbt (Saint Asky)** - Julien Schmidt <github@julienschmidt.com>
- Fix typo in example - Jun Kimura <jksmphone@gmail.com>
- Justin Beckwith <justin.beckwith@gmail.com>
- Justin Israel <justinisrael@gmail.com>
**@lucas-clemente (Lucas Clemente)** - Justin Mayhew <mayhew@live.ca>
- ★ work around path.Join removing trailing slashes from routes - Jérôme Laforge <jerome-laforge@users.noreply.github.com>
- Kacper Bąk <56700396+53jk1@users.noreply.github.com>
- Kamron Batman <kamronbatman@users.noreply.github.com>
**@mattn (Yasuhiro Matsumoto)** - Kane Rogers <kane@cleanstream.com.au>
- Improve color logger - Kaushik Neelichetty <kaushikneelichetty6132@gmail.com>
- Keiji Yoshida <yoshida.keiji.84@gmail.com>
- Kel Cecil <kel.cecil@listhub.com>
**@mdigger (Dmitry Sedykh)** - Kevin Mulvey <kmulvey@linux.com>
- Fixes Form binding when content-type is x-www-form-urlencoded - Kevin Zhu <ipandtcp@gmail.com>
- No repeat call c.Writer.Status() in gin.Logger - Kirill Motkov <motkov.kirill@gmail.com>
- Fixes Content-Type for json render - Klemen Sever <ksever@student.42.fr>
- Kristoffer A. Iversen <kristoffer.a.iversen@gmail.com>
- Krzysztof Szafrański <k.p.szafranski@gmail.com>
**@mirzac (Mirza Ceric)** - Kumar McMillan <kumar.mcmillan@gmail.com>
- Fix debug printing - Kyle Mcgill <email@kylescottmcgill.com>
- Lanco <35420416+lancoLiu@users.noreply.github.com>
- Levi Olson <olson.levi@gmail.com>
**@mopemope (Yutaka Matsubara)** - Lin Kao-Yuan <mosdeo@gmail.com>
- ★ Adds Godep support (Dependencies Manager) - Linus Unnebäck <linus@folkdatorn.se>
- Fix variadic parameter in the flexible render API - Lucas Clemente <lucas@clemente.io>
- Fix Corrupted plain render - Ludwig Valda Vasquez <bredov@gmail.com>
- Add Pluggable View Renderer Example - Luis GG <lggomez@users.noreply.github.com>
- MW Lim <williamchange@gmail.com>
- Maksimov Sergey <konjoot@gmail.com>
**@msemenistyi (Mykyta Semenistyi)** - Manjusaka <lizheao940510@gmail.com>
- update Readme.md. Add code to String method - Manu MA <manu.mtza@gmail.com>
- Manu MA <manu.valladolid@gmail.com>
- Manu Mtz-Almeida <manu.valladolid@gmail.com>
**@msoedov (Sasha Myasoedov)** - Manu Mtz.-Almeida <manu.valladolid@gmail.com>
- ★ Adds tons of unit tests. - Manuel Alonso <manuelalonso@invisionapp.com>
- Mara Kim <hacker.root@gmail.com>
- Mario Kostelac <mario@intercom.io>
**@ngerakines (Nick Gerakines)** - Martin Karlsch <martin@karlsch.com>
- ★ Improves API, c.GET() doesn't panic - Matt Newberry <mnewberry@opentable.com>
- Adds MustGet() method - Matt Williams <gh@mattyw.net>
- Matthieu MOREL <mmorel-35@users.noreply.github.com>
- Max Hilbrunner <mhilbrunner@users.noreply.github.com>
**@r8k (Rajiv Kilaparti)** - Maxime Soulé <btik-git@scoubidou.com>
- Fix Port usage in README. - MetalBreaker <johnymichelson@gmail.com>
- Michael Puncel <mpuncel@squareup.com>
- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com>
**@rayrod2030 (Ray Rodriguez)** - Mike <38686456+icy4ever@users.noreply.github.com>
- Fix typo in example - Mike Stipicevic <mst@ableton.com>
- Miki Tebeka <miki.tebeka@gmail.com>
- Miles <MilesLin@users.noreply.github.com>
**@rns** - Mirza Ceric <mirza.ceric@b2match.com>
- Fix typo in example - Mykyta Semenistyi <nikeiwe@gmail.com>
- Naoki Takano <honten@tinkermode.com>
- Ngalim Siregar <ngalim.siregar@gmail.com>
**@RobAWilkinson (Robert Wilkinson)** - Ni Hao <supernihaooo@qq.com>
- Add example of forms and params - Nick Gerakines <nick@gerakines.net>
- Nikifor Seryakov <nikandfor@gmail.com>
- Notealot <714804968@qq.com>
**@rogierlommers (Rogier Lommers)** - Olivier Mengué <dolmen@cpan.org>
- Add updated static serve example - Olivier Robardet <orobardet@users.noreply.github.com>
- Pablo Moncada <pablo.moncada@bq.com>
**@rw-access (Ross Wolf)** - Pablo Moncada <pmoncadaisla@gmail.com>
- Added support to mix exact and param routes - Panmax <967168@qq.com>
- Peperoncino <2wua4nlyi@gmail.com>
**@se77en (Damon Zhao)** - Philipp Meinen <philipp@bind.ch>
- Improve color logging - Pierre Massat <pierre@massat.io>
- Qt <golang.chen@gmail.com>
- Quentin ROYER <aydendevg@gmail.com>
**@silasb (Silas Baronda)** - README Bot <35302948+codetriage-readme-bot@users.noreply.github.com>
- Fixing quotes in README - Rafal Zajac <rzajac@gmail.com>
- Rahul Datta Roy <rahuldroy@users.noreply.github.com>
- Rajiv Kilaparti <rajivk085@gmail.com>
**@SkuliOskarsson (Skuli Oskarsson)** - Raphael Gavache <raphael.gavache@datadoghq.com>
- Fixes some texts in README II - Ray Rodriguez <rayrod2030@gmail.com>
- Regner Blok-Andersen <shadowdf@gmail.com>
- Remco <remco@dutchcoders.io>
**@slimmy (Jimmy Pettersson)** - Rex Lee(李俊) <duguying2008@gmail.com>
- Added messages for required bindings - Richard Lee <dlackty@gmail.com>
- Riverside <wangyb65@gmail.com>
- Robert Wilkinson <wilkinson.robert.a@gmail.com>
**@smira (Andrey Smirnov)** - Rogier Lommers <rogier@lommers.org>
- Add support for ignored/unexported fields in binding - Rohan Pai <me@rohanpai.com>
- Romain Beuque <rbeuque74@gmail.com>
- Roman Belyakovsky <ihryamzik@gmail.com>
**@superalsrk (SRK.Lyu)** - Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com>
- Update httprouter godeps - Roman Zaynetdinov <roman.zaynetdinov@lekane.com>
- Ronald Petty <ronald.petty@rx-m.com>
- Ross Wolf <31489089+rw-access@users.noreply.github.com>
**@tebeka (Miki Tebeka)** - Roy Lou <roylou@gmail.com>
- Use net/http constants instead of numeric values - Rubi <14269809+codenoid@users.noreply.github.com>
- Ryan <46182144+ryanker@users.noreply.github.com>
- Ryan J. Yoder <me@ryanjyoder.com>
**@techjanitor** - SRK.Lyu <superalsrk@gmail.com>
- Update context.go reserved IPs - Sai <sairoutine@gmail.com>
- Samuel Abreu <sdepaula@gmail.com>
- Santhosh Kumar <santhoshkumarr1096@gmail.com>
**@yosssi (Keiji Yoshida)** - Sasha Melentyev <sasha@melentyev.io>
- Fix link in README - Sasha Myasoedov <msoedov@gmail.com>
- Segev Finer <segev208@gmail.com>
- Sergey Egorov <egorovhome@gmail.com>
**@yuyabee** - Sergey Fedchenko <seregayoga@bk.ru>
- Fixed README - 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,5 +1,53 @@
# 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 ## Gin v1.7.7
### BUGFIXES ### BUGFIXES

View File

@ -11,7 +11,7 @@ TESTTAGS ?= ""
test: test:
echo "mode: count" > coverage.out echo "mode: count" > coverage.out
for d in $(TESTFOLDER); do \ for d in $(TESTFOLDER); do \
$(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ $(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
cat tmp.out; \ cat tmp.out; \
if grep -q "^--- FAIL" tmp.out; then \ if grep -q "^--- FAIL" tmp.out; then \
rm tmp.out; \ rm tmp.out; \

169
README.md
View File

@ -14,7 +14,6 @@
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
## Contents ## Contents
- [Gin Web Framework](#gin-web-framework) - [Gin Web Framework](#gin-web-framework)
@ -23,7 +22,7 @@ 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/go-json](#build-with-json-replacement) - [Build with json replacement](#build-with-json-replacement)
- [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - [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)
@ -38,6 +37,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Grouping routes](#grouping-routes) - [Grouping routes](#grouping-routes)
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
- [Using middleware](#using-middleware) - [Using middleware](#using-middleware)
- [Custom Recovery behavior](#custom-recovery-behavior)
- [How to write log file](#how-to-write-log-file) - [How to write log file](#how-to-write-log-file)
- [Custom Log Format](#custom-log-format) - [Custom Log Format](#custom-log-format)
- [Controlling Log output coloring](#controlling-log-output-coloring) - [Controlling Log output coloring](#controlling-log-output-coloring)
@ -75,6 +75,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Build a single binary with templates](#build-a-single-binary-with-templates) - [Build a single binary with templates](#build-a-single-binary-with-templates)
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
- [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)
@ -86,10 +87,10 @@ 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.14+ 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
``` ```
2. Import it in your code: 2. Import it in your code:
@ -114,12 +115,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",
}) })
}) })
@ -189,12 +194,21 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
Gin uses `encoding/json` as default json package but you can change it 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) [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) [go-json](https://github.com/goccy/go-json)
```sh ```sh
$ go build -tags=go_json . go build -tags=go_json .
```
[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
```sh
$ go build -tags="sonic avx" .
``` ```
## Build without `MsgPack` rendering feature ## Build without `MsgPack` rendering feature
@ -202,7 +216,7 @@ $ go build -tags=go_json .
Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
```sh ```sh
$ go build -tags=nomsgpack . 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). This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
@ -300,7 +314,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,
@ -312,7 +326,7 @@ func main() {
### Another example: query + post form ### Another example: query + post form
``` ```sh
POST /post?id=1234&page=1 HTTP/1.1 POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
@ -336,13 +350,13 @@ func main() {
} }
``` ```
``` ```sh
id: 1234; page: 1; name: manu; message: this_is_great id: 1234; page: 1; name: manu; message: this_is_great
``` ```
### Map as querystring or postform parameters ### Map as querystring or postform parameters
``` ```sh
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
@ -364,7 +378,7 @@ func main() {
} }
``` ```
``` ```sh
ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
``` ```
@ -481,8 +495,8 @@ instead of
r := gin.Default() r := gin.Default()
``` ```
### Using middleware ### Using middleware
```go ```go
func main() { func main() {
// Creates a router without any middleware by default // Creates a router without any middleware by default
@ -523,6 +537,7 @@ func main() {
``` ```
### Custom Recovery behavior ### Custom Recovery behavior
```go ```go
func main() { func main() {
// Creates a router without any middleware by default // Creates a router without any middleware by default
@ -556,6 +571,7 @@ func main() {
``` ```
### How to write log file ### How to write log file
```go ```go
func main() { func main() {
// Disable Console Color, you don't need console color when writing the logs to file. // Disable Console Color, you don't need console color when writing the logs to file.
@ -570,7 +586,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")
@ -578,6 +594,7 @@ func main() {
``` ```
### Custom Log Format ### Custom Log Format
```go ```go
func main() { func main() {
router := gin.New() router := gin.New()
@ -602,15 +619,16 @@ 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")
} }
``` ```
**Sample Output** Sample Output
```
```sh
::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" "
``` ```
@ -630,7 +648,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")
@ -649,7 +667,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")
@ -658,18 +676,19 @@ func main() {
### Model binding and validation ### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
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`.
@ -745,8 +764,9 @@ func main() {
} }
``` ```
**Sample request** Sample request
```shell
```sh
$ curl -v -X POST \ $ curl -v -X POST \
http://localhost:8080/loginJSON \ http://localhost:8080/loginJSON \
-H 'content-type: application/json' \ -H 'content-type: application/json' \
@ -767,9 +787,7 @@ $ curl -v -X POST \
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
``` ```
**Skip validate** Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
### Custom Validators ### Custom Validators
@ -848,6 +866,7 @@ package main
import ( import (
"log" "log"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -870,7 +889,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")
} }
``` ```
@ -884,6 +903,7 @@ package main
import ( import (
"log" "log"
"net/http"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -916,13 +936,14 @@ func startPage(c *gin.Context) {
log.Println(person.UnixTime) log.Println(person.UnixTime)
} }
c.String(200, "Success") c.String(http.StatusOK, "Success")
} }
``` ```
Test it with: Test it with:
```sh ```sh
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
``` ```
### Bind Uri ### Bind Uri
@ -932,7 +953,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"`
@ -944,19 +969,20 @@ 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.Error()}) 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")
} }
``` ```
Test it with: Test it with:
```sh ```sh
$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid curl -v localhost:8088/thinkerou/not-uuid
``` ```
### Bind Header ### Bind Header
@ -966,6 +992,8 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -980,11 +1008,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()
@ -1014,7 +1042,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})
} }
... ...
@ -1038,7 +1066,7 @@ form.html
result: result:
``` ```json
{"color":["red","green","blue"]} {"color":["red","green","blue"]}
``` ```
@ -1081,8 +1109,9 @@ func main() {
``` ```
Test it with: Test it with:
```sh ```sh
$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
``` ```
### XML, JSON, YAML and ProtoBuf rendering ### XML, JSON, YAML and ProtoBuf rendering
@ -1159,6 +1188,7 @@ func main() {
r.Run(":8080") r.Run(":8080")
} }
``` ```
#### JSONP #### JSONP
Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
@ -1219,14 +1249,14 @@ func main() {
// Serves unicode entities // Serves unicode entities
r.GET("/json", func(c *gin.Context) { r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"html": "<b>Hello, world!</b>", "html": "<b>Hello, world!</b>",
}) })
}) })
// Serves literal characters // Serves literal characters
r.GET("/purejson", func(c *gin.Context) { r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{ c.PureJSON(http.StatusOK, gin.H{
"html": "<b>Hello, world!</b>", "html": "<b>Hello, world!</b>",
}) })
}) })
@ -1440,7 +1470,8 @@ Date: {[{.now | formatAsDate}]}
``` ```
Result: Result:
```
```sh
Date: 2017/07/01 Date: 2017/07/01
``` ```
@ -1459,6 +1490,7 @@ r.GET("/test", func(c *gin.Context) {
``` ```
Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444)
```go ```go
r.POST("/test", func(c *gin.Context) { r.POST("/test", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/foo") c.Redirect(http.StatusFound, "/foo")
@ -1473,11 +1505,10 @@ 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"})
}) })
``` ```
### Custom Middleware ### Custom Middleware
```go ```go
@ -1600,6 +1631,7 @@ func main() {
http.ListenAndServe(":8080", router) http.ListenAndServe(":8080", router)
} }
``` ```
or or
```go ```go
@ -1626,6 +1658,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"
@ -1636,7 +1669,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"))
@ -1650,6 +1683,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"
@ -1661,7 +1695,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{
@ -1780,9 +1814,9 @@ endless.ListenAndServe(":4242", router)
Alternatives: Alternatives:
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. * [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
#### Manually #### Manually
@ -1922,7 +1956,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,
}) })
@ -1931,7 +1965,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,
}) })
@ -1940,7 +1974,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,
}) })
@ -1958,7 +1992,7 @@ func main() {
Using the command `curl` command result: Using the command `curl` command result:
``` ```sh
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" $ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":"hello"},"b":"world"} {"a":{"FieldA":"hello"},"b":"world"}
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" $ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
@ -2017,10 +2051,10 @@ func SomeHandler(c *gin.Context) {
} }
``` ```
* `c.ShouldBindBodyWith` stores body into the context before binding. This has 1. `c.ShouldBindBodyWith` stores body into the context before binding. This has
a slight impact to performance, so you should not use this method if you are a slight impact to performance, so you should not use this method if you are
enough to call binding at once. enough to call binding at once.
* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, 2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, `ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
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)).
@ -2090,6 +2124,7 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -2118,7 +2153,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",
}) })
}) })
@ -2131,7 +2166,8 @@ func main() {
### Define format for the log of routes ### Define format for the log of routes
The default log of routes is: The default log of routes is:
```
```sh
[GIN-debug] POST /foo --> main.main.func1 (3 handlers) [GIN-debug] POST /foo --> main.main.func1 (3 handlers)
[GIN-debug] GET /bar --> main.main.func2 (3 handlers) [GIN-debug] GET /bar --> main.main.func2 (3 handlers)
[GIN-debug] GET /status --> main.main.func3 (3 handlers) [GIN-debug] GET /status --> main.main.func3 (3 handlers)
@ -2139,6 +2175,7 @@ The default log of routes is:
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.
In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
```go ```go
import ( import (
"log" "log"
@ -2242,6 +2279,7 @@ func main() {
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` **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. to skip TrustedProxies check, it has a higher priority than TrustedProxies.
Look at the example below: Look at the example below:
```go ```go
import ( import (
"fmt" "fmt"
@ -2274,10 +2312,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
} }
@ -2305,10 +2349,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())
} }
``` ```
@ -2324,4 +2368,3 @@ 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.

View File

@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false return "", false
} }
for _, pair := range a { for _, pair := range a {
if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 { if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
return pair.user, true return pair.user, true
} }
} }

View File

@ -22,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
@ -83,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
@ -103,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:

View File

@ -20,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
@ -79,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
@ -99,6 +101,8 @@ 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
} }

View File

@ -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) {
@ -454,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",
@ -1339,10 +1356,10 @@ 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)
invalid_obj := FooStruct{} invalidobj := FooStruct{}
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", MIMEPROTOBUF)
err = b.Bind(req, &invalid_obj) err = b.Bind(req, &invalidobj)
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, err.Error(), "obj is not ProtoMessage") assert.Equal(t, err.Error(), "obj is not ProtoMessage")

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 (

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 (

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

@ -34,11 +34,15 @@ 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"
// 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. // abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1 const abortIndex int8 = math.MaxInt8 >> 1
@ -98,6 +102,7 @@ func (c *Context) reset() {
c.Accepted = nil c.Accepted = nil
c.queryCache = nil c.queryCache = nil
c.formCache = nil c.formCache = nil
c.sameSite = 0
*c.params = (*c.params)[:0] *c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0] *c.skippedNodes = (*c.skippedNodes)[:0]
} }
@ -148,6 +153,7 @@ func (c *Context) Handler() HandlerFunc {
// FullPath returns a matched route full path. For not found routes // FullPath returns a matched route full path. For not found routes
// returns an empty string. // returns an empty string.
//
// router.GET("/user/:id", func(c *gin.Context) { // router.GET("/user/:id", func(c *gin.Context) {
// c.FullPath() == "/user/:id" // true // c.FullPath() == "/user/:id" // true
// }) // })
@ -377,6 +383,7 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
// Param returns the value of the URL param. // Param returns the value of the URL param.
// It is a shortcut for c.Params.ByName(key) // It is a shortcut for c.Params.ByName(key)
//
// router.GET("/user/:id", func(c *gin.Context) { // router.GET("/user/:id", func(c *gin.Context) {
// // a GET request to /user/john // // a GET request to /user/john
// id := c.Param("id") // id == "john" // id := c.Param("id") // id == "john"
@ -397,6 +404,7 @@ func (c *Context) AddParam(key, value string) {
// 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)`
//
// GET /path?id=1234&name=Manu&value= // GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234" // c.Query("id") == "1234"
// c.Query("name") == "Manu" // c.Query("name") == "Manu"
@ -410,6 +418,7 @@ func (c *Context) Query(key string) (value string) {
// DefaultQuery returns the keyed url query value if it exists, // DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string. // otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information. // See: Query() and GetQuery() for further information.
//
// GET /?name=Manu&lastname= // GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu" // c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none" // c.DefaultQuery("id", "none") == "none"
@ -425,6 +434,7 @@ func (c *Context) DefaultQuery(key, defaultValue string) string {
// if it exists `(value, true)` (even when the value is an empty string), // if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`. // otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)` // It is shortcut for `c.Request.URL.Query().Get(key)`
//
// GET /?name=Manu&lastname= // GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name") // ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id") // ("", false) == c.GetQuery("id")
@ -495,6 +505,7 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string {
// form or multipart form when it exists `(value, true)` (even when the value is an empty string), // form or multipart form when it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns ("", false). // otherwise it returns ("", false).
// For example, during a PATCH request to update the user's email: // For example, during a PATCH request to update the user's email:
//
// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" // email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
// email= --> ("", true) := GetPostForm("email") // set email to "" // email= --> ("", true) := GetPostForm("email") // set email to ""
// --> ("", false) := GetPostForm("email") // do nothing with email // --> ("", false) := GetPostForm("email") // do nothing with email
@ -602,8 +613,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
// Bind checks the Method and Content-Type to select a binding engine automatically, // Bind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example: // 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
//
// 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.
@ -632,6 +645,11 @@ 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 any) error { func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header) return c.MustBindWith(obj, binding.Header)
@ -660,8 +678,10 @@ func (c *Context) MustBindWith(obj any, b binding.Binding) error {
// ShouldBind checks the Method and Content-Type to select a binding engine automatically, // ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example: // 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
//
// 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 or abort if input 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.
@ -690,6 +710,11 @@ 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 any) error { func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header) return c.ShouldBindWith(obj, binding.Header)
@ -733,7 +758,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error
} }
// ClientIP implements one best effort algorithm to return the real client IP. // ClientIP implements one best effort algorithm to return the real client IP.
// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-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, // 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. // the remote IP (coming from Request.RemoteAddr) is returned.
@ -961,6 +986,11 @@ 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 any) { func (c *Context) ProtoBuf(code int, obj any) {
c.Render(code, render.ProtoBuf{Data: obj}) c.Render(code, render.ProtoBuf{Data: obj})
@ -1065,6 +1095,7 @@ type Negotiate struct {
XMLData any XMLData any
YAMLData any YAMLData any
Data any Data any
TOMLData any
} }
// Negotiate calls different Render according to acceptable Accept format. // Negotiate calls different Render according to acceptable Accept format.
@ -1086,6 +1117,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
} }
@ -1133,7 +1168,7 @@ func (c *Context) SetAccepted(formats ...string) {
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context. // Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
func (c *Context) Deadline() (deadline time.Time, ok bool) { func (c *Context) Deadline() (deadline time.Time, ok bool) {
if c.Request == nil || c.Request.Context() == nil { if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return return
} }
return c.Request.Context().Deadline() return c.Request.Context().Deadline()
@ -1141,7 +1176,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) {
// Done returns nil (chan which will wait forever) when c.Request has no Context. // Done returns nil (chan which will wait forever) when c.Request has no Context.
func (c *Context) Done() <-chan struct{} { func (c *Context) Done() <-chan struct{} {
if c.Request == nil || c.Request.Context() == nil { if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil return nil
} }
return c.Request.Context().Done() return c.Request.Context().Done()
@ -1149,7 +1184,7 @@ func (c *Context) Done() <-chan struct{} {
// Err returns nil when c.Request has no Context. // Err returns nil when c.Request has no Context.
func (c *Context) Err() error { func (c *Context) Err() error {
if c.Request == nil || c.Request.Context() == nil { if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil return nil
} }
return c.Request.Context().Err() return c.Request.Context().Err()
@ -1162,12 +1197,15 @@ func (c *Context) Value(key any) any {
if key == 0 { if key == 0 {
return c.Request return c.Request
} }
if key == ContextKey {
return c
}
if keyAsString, ok := key.(string); ok { if keyAsString, ok := key.(string); ok {
if val, exists := c.Get(keyAsString); exists { if val, exists := c.Get(keyAsString); exists {
return val return val
} }
} }
if c.Request == nil || c.Request.Context() == nil { if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
return nil return nil
} }
return c.Request.Context().Value(key) return c.Request.Context().Value(key)

View File

@ -17,6 +17,16 @@ import (
"github.com/stretchr/testify/assert" "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) { func TestContextFormFileFailed17(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
@ -31,3 +41,32 @@ func TestContextFormFileFailed17(t *testing.T) {
assert.Nil(t, f) 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

@ -1060,6 +1060,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
@ -1180,6 +1193,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)
@ -1640,6 +1683,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)
@ -1773,6 +1833,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)
@ -1880,6 +1957,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")
@ -2079,12 +2157,18 @@ func TestRemoteIPFail(t *testing.T) {
} }
func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
deadline, ok := c.Deadline() deadline, ok := c.Deadline()
assert.Zero(t, deadline) assert.Zero(t, deadline)
assert.False(t, ok) assert.False(t, ok)
c2 := &Context{} c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
d := time.Now().Add(time.Second) d := time.Now().Add(time.Second)
ctx, cancel := context.WithDeadline(context.Background(), d) ctx, cancel := context.WithDeadline(context.Background(), d)
@ -2096,10 +2180,16 @@ func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
} }
func TestContextWithFallbackDoneFromRequestContext(t *testing.T) { func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
assert.Nil(t, c.Done()) assert.Nil(t, c.Done())
c2 := &Context{} c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c2.Request = c2.Request.WithContext(ctx) c2.Request = c2.Request.WithContext(ctx)
@ -2108,10 +2198,16 @@ func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
} }
func TestContextWithFallbackErrFromRequestContext(t *testing.T) { func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
assert.Nil(t, c.Err()) assert.Nil(t, c.Err())
c2 := &Context{} c2, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c2.engine.ContextWithFallback = true
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c2.Request = c2.Request.WithContext(ctx) c2.Request = c2.Request.WithContext(ctx)
@ -2120,9 +2216,9 @@ func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
assert.EqualError(t, c2.Err(), context.Canceled.Error()) assert.EqualError(t, c2.Err(), context.Canceled.Error())
} }
func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
type contextKey string type contextKey string
func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
getContextAndKey func() (*Context, any) getContextAndKey func() (*Context, any)
@ -2132,7 +2228,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
name: "c with struct context key", name: "c with struct context key",
getContextAndKey: func() (*Context, any) { getContextAndKey: func() (*Context, any) {
var key struct{} var key struct{}
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value")) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value"))
return c, key return c, key
@ -2142,7 +2240,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
{ {
name: "c with string context key", name: "c with string context key",
getContextAndKey: func() (*Context, any) { getContextAndKey: func() (*Context, any) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
return c, contextKey("key") return c, contextKey("key")
@ -2152,7 +2252,10 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
{ {
name: "c with nil http.Request", name: "c with nil http.Request",
getContextAndKey: func() (*Context, any) { getContextAndKey: func() (*Context, any) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request = nil
return c, "key" return c, "key"
}, },
value: nil, value: nil,
@ -2160,7 +2263,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
{ {
name: "c with nil http.Request.Context()", name: "c with nil http.Request.Context()",
getContextAndKey: func() (*Context, any) { getContextAndKey: func() (*Context, any) {
c := &Context{} c, _ := CreateTestContext(httptest.NewRecorder())
// enable ContextWithFallback feature flag
c.engine.ContextWithFallback = true
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
return c, "key" return c, "key"
}, },
@ -2175,6 +2280,70 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
} }
} }
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) { func TestContextAddParam(t *testing.T) {
c := &Context{} c := &Context{}
id := "id" id := "id"

View File

@ -12,7 +12,7 @@ import (
"strings" "strings"
) )
const ginSupportMinGoVer = 14 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.
@ -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.14+. debugPrint(`[WARNING] Now Gin requires Go 1.15+.
`) `)
} }

View 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.14+.\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

@ -124,6 +124,7 @@ func (a errorMsgs) Last() *Error {
// Errors returns an array with 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"))
// c.Error(errors.New("third")) // c.Error(errors.New("third"))

5
gin.go
View File

@ -147,6 +147,9 @@ type Engine struct {
// UseH2C enable h2c support. // UseH2C enable h2c support.
UseH2C bool 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
@ -195,7 +198,7 @@ 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"}, trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs, trustedCIDRs: defaultTrustedCIDRs,
} }
engine.RouterGroup.engine = engine engine.RouterGroup.engine = engine

View File

@ -108,6 +108,7 @@ func StaticFile(relativePath, filepath string) gin.IRoutes {
// of the Router's NotFound handler. // of the Router's NotFound handler.
// To use the operating system's file system implementation, // To use the operating system's file system implementation,
// use : // use :
//
// router.Static("/static", "/var/www") // router.Static("/static", "/var/www")
func Static(relativePath, root string) gin.IRoutes { func Static(relativePath, root string) gin.IRoutes {
return engine().Static(relativePath, root) return engine().Static(relativePath, root)

View File

@ -15,6 +15,7 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -281,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
listener, err := net.ListenTCP("tcp", addr) listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err) assert.NoError(t, err)
socketFile, err := listener.File() socketFile, err := listener.File()
if isWindows() {
// not supported by windows, it is unimplemented now
assert.Error(t, err)
} else {
assert.NoError(t, err) assert.NoError(t, err)
}
if socketFile == nil {
return
}
go func() { go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@ -547,3 +557,7 @@ func TestTreeRunDynamicRouting(t *testing.T) {
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found") testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found") testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
} }
func isWindows() bool {
return runtime.GOOS == "windows"
}

View File

@ -202,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() {
@ -320,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) {

View File

@ -296,8 +296,8 @@ 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")
}) })
@ -318,8 +318,8 @@ 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")
}) })

18
go.mod
View File

@ -3,28 +3,34 @@ module github.com/gin-gonic/gin
go 1.18 go 1.18
require ( require (
github.com/bytedance/sonic v1.3.4
github.com/gin-contrib/sse v0.1.0 github.com/gin-contrib/sse v0.1.0
github.com/go-playground/validator/v10 v10.10.0 github.com/go-playground/validator/v10 v10.10.0
github.com/goccy/go-json v0.9.5 github.com/goccy/go-json v0.9.10
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-isatty v0.0.16
github.com/stretchr/testify v1.7.0 github.com/pelletier/go-toml/v2 v2.0.2
github.com/stretchr/testify v1.8.0
github.com/ugorji/go/codec v1.2.7 github.com/ugorji/go/codec v1.2.7
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
google.golang.org/protobuf v1.27.1 google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require ( require (
github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.14 // indirect
github.com/leodido/go-urn v1.2.1 // 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/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.3.6 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

51
go.sum
View File

@ -1,3 +1,8 @@
github.com/bytedance/sonic v1.3.4 h1:Pq+4YeIBh5VKMctAwqeiAsf18BCU24wZnwecwjIUCvU=
github.com/bytedance/sonic v1.3.4/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI=
github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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=
@ -12,14 +17,18 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik= github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8=
github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@ -30,12 +39,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/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/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=
@ -43,22 +54,40 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 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/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU=
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
@ -67,8 +96,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 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= 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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -77,5 +106,7 @@ 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -2,8 +2,10 @@
// 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 && !go_json //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
// +build !jsoniter,!go_json // +build !jsoniter
// +build !go_json
// +build !sonic !avx !linux,!windows,!darwin !amd64
package json package json

27
internal/json/sonic.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2022 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build sonic && avx && (linux || windows || darwin) && amd64
// +build sonic
// +build avx
// +build linux windows darwin
// +build amd64
package json
import "github.com/bytedance/sonic"
var (
json = sonic.ConfigStd
// Marshal is exported by gin/json package.
Marshal = json.Marshal
// Unmarshal is exported by gin/json package.
Unmarshal = json.Unmarshal
// MarshalIndent is exported by gin/json package.
MarshalIndent = json.MarshalIndent
// NewDecoder is exported by gin/json package.
NewDecoder = json.NewDecoder
// NewEncoder is exported by gin/json package.
NewEncoder = json.NewEncoder
)

View File

@ -208,6 +208,7 @@ 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")

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"flag"
"io" "io"
"os" "os"
@ -34,6 +35,7 @@ const (
// Note that both Logger and Recovery provides custom ways to configure their // Note that both Logger and Recovery provides custom ways to configure their
// output io.Writer. // output io.Writer.
// To support coloring in Windows use: // To support coloring in Windows use:
//
// import "github.com/mattn/go-colorable" // import "github.com/mattn/go-colorable"
// gin.DefaultWriter = colorable.NewColorableStdout() // gin.DefaultWriter = colorable.NewColorableStdout()
var DefaultWriter io.Writer = os.Stdout var DefaultWriter io.Writer = os.Stdout
@ -54,8 +56,12 @@ func init() {
// SetMode sets gin mode according to input string. // SetMode sets gin mode according to input string.
func SetMode(value string) { func SetMode(value string) {
if value == "" { if value == "" {
if flag.Lookup("test.v") != nil {
value = TestMode
} else {
value = DebugMode value = DebugMode
} }
}
switch value { switch value {
case DebugMode: case DebugMode:

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"flag"
"os" "os"
"testing" "testing"
@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
assert.Equal(t, TestMode, Mode()) assert.Equal(t, TestMode, Mode())
os.Unsetenv(EnvGinMode) os.Unsetenv(EnvGinMode)
SetMode("")
assert.Equal(t, testCode, ginMode)
assert.Equal(t, TestMode, Mode())
tmp := flag.CommandLine
flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
SetMode("") SetMode("")
assert.Equal(t, debugCode, ginMode) assert.Equal(t, debugCode, ginMode)
assert.Equal(t, DebugMode, Mode()) assert.Equal(t, DebugMode, Mode())
flag.CommandLine = tmp
SetMode(DebugMode) SetMode(DebugMode)
assert.Equal(t, debugCode, ginMode) assert.Equal(t, debugCode, ginMode)

View 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) {

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

@ -182,6 +182,7 @@ func (group *RouterGroup) staticFileHandler(relativePath string, handler Handler
// of the Router's NotFound handler. // of the Router's NotFound handler.
// To use the operating system's file system implementation, // To use the operating system's file system implementation,
// use : // use :
//
// router.Static("/static", "/var/www") // router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) IRoutes { func (group *RouterGroup) Static(relativePath, root string) IRoutes {
return group.StaticFS(relativePath, Dir(root, false)) return group.StaticFS(relativePath, Dir(root, false))

View File

@ -455,7 +455,7 @@ walk: // Outer loop for walking the tree
if !n.wildChild { if !n.wildChild {
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes // If the path at the end of the loop is not equal to '/' and the current node has no child nodes
// the current node needs to roll back to last vaild skippedNode // the current node needs to roll back to last valid skippedNode
if path != "/" { if path != "/" {
for l := len(*skippedNodes); l > 0; { for l := len(*skippedNodes); l > 0; {
skippedNode := (*skippedNodes)[l-1] skippedNode := (*skippedNodes)[l-1]
@ -572,7 +572,7 @@ walk: // Outer loop for walking the tree
if path == prefix { if path == prefix {
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
// the current node needs to roll back to last vaild skippedNode // the current node needs to roll back to last valid skippedNode
if n.handlers == nil && path != "/" { if n.handlers == nil && path != "/" {
for l := len(*skippedNodes); l > 0; { for l := len(*skippedNodes); l > 0; {
skippedNode := (*skippedNodes)[l-1] skippedNode := (*skippedNodes)[l-1]

View File

@ -5,4 +5,4 @@
package gin package gin
// Version is the current gin framework's version. // Version is the current gin framework's version.
const Version = "v1.7.7" const Version = "v1.8.1"