mirror of
https://github.com/gin-gonic/gin.git
synced 2025-04-06 03:57:46 +08:00
Merge branch 'gin-gonic:master' into master
This commit is contained in:
commit
b34e0714b4
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -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
|
||||||
|
18
.github/workflows/gin.yml
vendored
18
.github/workflows/gin.yml
vendored
@ -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
34
.github/workflows/goreleaser.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: Goreleaser
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
-
|
||||||
|
name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.17
|
||||||
|
-
|
||||||
|
name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v3
|
||||||
|
with:
|
||||||
|
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||||
|
distribution: goreleaser
|
||||||
|
version: latest
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
57
.goreleaser.yaml
Normal file
57
.goreleaser.yaml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
project_name: gin
|
||||||
|
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
# If true, skip the build.
|
||||||
|
# Useful for library projects.
|
||||||
|
# Default is false
|
||||||
|
skip: true
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
# Set it to true if you wish to skip the changelog generation.
|
||||||
|
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||||
|
skip: false
|
||||||
|
|
||||||
|
# Changelog generation implementation to use.
|
||||||
|
#
|
||||||
|
# Valid options are:
|
||||||
|
# - `git`: uses `git log`;
|
||||||
|
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
|
||||||
|
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
|
||||||
|
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||||
|
#
|
||||||
|
# Defaults to `git`.
|
||||||
|
use: git
|
||||||
|
|
||||||
|
# Sorts the changelog by the commit's messages.
|
||||||
|
# Could either be asc, desc or empty
|
||||||
|
# Default is empty
|
||||||
|
sort: asc
|
||||||
|
|
||||||
|
# Group commits messages by given regex and title.
|
||||||
|
# Order value defines the order of the groups.
|
||||||
|
# Proving no regex means all commits will be grouped under the default group.
|
||||||
|
# Groups are disabled when using github-native, as it already groups things by itself.
|
||||||
|
#
|
||||||
|
# Default is no groups.
|
||||||
|
groups:
|
||||||
|
- title: Features
|
||||||
|
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||||
|
order: 0
|
||||||
|
- title: 'Bug fixes'
|
||||||
|
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||||
|
order: 1
|
||||||
|
- title: 'Enhancements'
|
||||||
|
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||||
|
order: 2
|
||||||
|
- title: Others
|
||||||
|
order: 999
|
||||||
|
|
||||||
|
filters:
|
||||||
|
# Commit messages matching the regexp listed here will be removed from
|
||||||
|
# the changelog
|
||||||
|
# Default is empty
|
||||||
|
exclude:
|
||||||
|
- '^docs'
|
||||||
|
- 'CICD'
|
||||||
|
- typo
|
622
AUTHORS.md
622
AUTHORS.md
@ -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>
|
||||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@ -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
|
||||||
|
2
Makefile
2
Makefile
@ -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
169
README.md
@ -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.
|
||||||
|
|
||||||
|
2
auth.go
2
auth.go
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
@ -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
35
binding/toml.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlBinding struct{}
|
||||||
|
|
||||||
|
func (tomlBinding) Name() string {
|
||||||
|
return "toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeToml(r io.Reader, obj any) error {
|
||||||
|
decoder := toml.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decoder.Decode(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeToml(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeToml(bytes.NewReader(body), obj)
|
||||||
|
}
|
22
binding/toml_test.go
Normal file
22
binding/toml_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTOMLBindingBindBody(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Foo string `toml:"foo"`
|
||||||
|
}
|
||||||
|
tomlBody := `foo="FOO"`
|
||||||
|
err := tomlBinding{}.BindBody([]byte(tomlBody), &s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "FOO", s.Foo)
|
||||||
|
}
|
48
context.go
48
context.go
@ -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)
|
||||||
|
@ -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"))
|
||||||
|
}
|
||||||
|
191
context_test.go
191
context_test.go
@ -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"
|
||||||
|
6
debug.go
6
debug.go
@ -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+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
5
gin.go
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
18
go.mod
@ -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
51
go.sum
@ -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=
|
||||||
|
@ -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
27
internal/json/sonic.go
Normal 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
|
||||||
|
)
|
@ -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")
|
||||||
|
|
||||||
|
6
mode.go
6
mode.go
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
36
render/toml.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TOML contains the given interface object.
|
||||||
|
type TOML struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var TOMLContentType = []string{"application/toml; charset=utf-8"}
|
||||||
|
|
||||||
|
// Render (TOML) marshals the given interface object and writes data with custom ContentType.
|
||||||
|
func (r TOML) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := toml.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (TOML) writes TOML ContentType for response.
|
||||||
|
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, TOMLContentType)
|
||||||
|
}
|
@ -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))
|
||||||
|
4
tree.go
4
tree.go
@ -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]
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user