diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index d909d22d..8857bebc 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -26,7 +26,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.9 + version: v2.11 args: --verbose test: needs: lint @@ -78,6 +78,6 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index a4c62bf4..9060e45c 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Run Trivy vulnerability scanner (source code) - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" scan-ref: "." @@ -44,7 +44,7 @@ jobs: sarif_file: "trivy-results.sarif" - name: Run Trivy scanner (table output for logs) - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 if: always() with: scan-type: "fs" diff --git a/AUTHORS.md b/AUTHORS.md deleted file mode 100644 index b4773ef3..00000000 --- a/AUTHORS.md +++ /dev/null @@ -1,406 +0,0 @@ -List of all the awesome people working to make Gin the best Web Framework in Go. - -## gin 1.x series authors - -**Gin Core Team:** Bo-Yi Wu (@appleboy), thinkerou (@thinkerou), Javier Provecho (@javierprovecho) - -## gin 0.x series authors - -**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) - ------- - -People and companies, who have contributed, in alphabetical order. - -- 178inaba <178inaba@users.noreply.github.com> -- A. F -- ABHISHEK SONI -- Abhishek Chanda -- Abner Chen -- AcoNCodes -- Adam Dratwinski -- Adam Mckaig -- Adam Zielinski -- Adonis -- Alan Wang -- Albin Gilles -- Aleksandr Didenko -- Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> -- Alex -- Alexander -- Alexander Lokhman -- Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> -- Alexander Nyquist -- Allen Ren -- AllinGo -- Ammar Bandukwala -- An Xiao (Luffy) -- Andre Dublin <81dublin@gmail.com> -- Andrew Szeto -- Andrey Abramov -- Andrey Nering -- Andrey Smirnov -- Andrii Bubis -- André Bazaglia -- Andy Pan -- Antoine GIRARD -- Anup Kumar Panwar <1anuppanwar@gmail.com> -- Aravinth Sundaram -- Artem -- Ashwani -- Aurelien Regat-Barrel -- Austin Heap -- Barnabus -- Bo-Yi Wu -- Boris Borshevsky -- Boyi Wu -- BradyBromley <51128276+BradyBromley@users.noreply.github.com> -- Brendan Fosberry -- Brian Wigginton -- Carlos Eduardo -- Chad Russell -- Charles -- Christian Muehlhaeuser -- Christian Persson -- Christopher Harrington -- Damon Zhao -- Dan Markham -- Dang Nguyen -- Daniel Krom -- Daniel M. Lambea -- Danieliu -- David Irvine -- David Zhang -- Davor Kapsa -- DeathKing -- Dennis Cho <47404603+forest747@users.noreply.github.com> -- Dmitry Dorogin -- Dmitry Kutakov -- Dmitry Sedykh -- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com> -- Donn Pebe -- Dustin Decker -- Eason Lin -- Edward Betts -- Egor Seredin <4819888+agmt@users.noreply.github.com> -- Emmanuel Goh -- Equim -- Eren A. Akyol -- Eric_Lee -- Erik Bender -- Ethan Kan -- Evgeny Persienko -- Faisal Alam -- Fareed Dudhia -- Filip Figiel -- Florian Polster -- Frank Bille -- Franz Bettag -- Ganlv -- Gaozhen Ying -- George Gabolaev -- George Kirilenko -- Georges Varouchas -- Gordon Tyler -- Harindu Perera -- Helios <674876158@qq.com> -- Henry Kwan -- Henry Yee -- Himanshu Mishra -- Hiroyuki Tanaka -- Ibraheem Ahmed -- Ignacio Galindo -- Igor H. Vieira -- Ildar1111 <54001462+Ildar1111@users.noreply.github.com> -- Iskander (Alex) Sharipov -- Ismail Gjevori -- Ivan Chen -- JINNOUCHI Yasushi -- James Pettyjohn -- Jamie Stackhouse -- Jason Lee -- Javier Provecho -- Javier Provecho -- Javier Provecho -- Javier Provecho Fernandez -- Javier Provecho Fernandez -- Jean-Christophe Lebreton -- Jeff -- Jeremy Loy -- Jim Filippou -- Jimmy Pettersson -- John Bampton -- Johnny Dallas -- Johnny Dallas -- Jonathan (JC) Chen -- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com> -- Josh Horowitz -- Joshua Loper -- Julien Schmidt -- Jun Kimura -- Justin Beckwith -- Justin Israel -- Justin Mayhew -- Jérôme Laforge -- Kacper Bąk <56700396+53jk1@users.noreply.github.com> -- Kamron Batman -- Kane Rogers -- Kaushik Neelichetty -- Keiji Yoshida -- Kel Cecil -- Kevin Mulvey -- Kevin Zhu -- Kirill Motkov -- Klemen Sever -- Kristoffer A. Iversen -- Krzysztof Szafrański -- Kumar McMillan -- Kyle Mcgill -- Lanco <35420416+lancoLiu@users.noreply.github.com> -- Levi Olson -- Lin Kao-Yuan -- Linus Unnebäck -- Lucas Clemente -- Ludwig Valda Vasquez -- Luis GG -- MW Lim -- Maksimov Sergey -- Manjusaka -- Manu MA -- Manu MA -- Manu Mtz-Almeida -- Manu Mtz.-Almeida -- Manuel Alonso -- Mara Kim -- Mario Kostelac -- Martin Karlsch -- Matt Newberry -- Matt Williams -- Matthieu MOREL -- Max Hilbrunner -- Maxime Soulé -- MetalBreaker -- Michael Puncel -- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com> -- Mike <38686456+icy4ever@users.noreply.github.com> -- Mike Stipicevic -- Miki Tebeka -- Miles -- Mirza Ceric -- Mykyta Semenistyi -- Naoki Takano -- Ngalim Siregar -- Ni Hao -- Nick Gerakines -- Nikifor Seryakov -- Notealot <714804968@qq.com> -- Olivier Mengué -- Olivier Robardet -- Pablo Moncada -- Pablo Moncada -- Panmax <967168@qq.com> -- Peperoncino <2wua4nlyi@gmail.com> -- Philipp Meinen -- Pierre Massat -- Qt -- Quentin ROYER -- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> -- Rafal Zajac -- Rahul Datta Roy -- Rajiv Kilaparti -- Raphael Gavache -- Ray Rodriguez -- Regner Blok-Andersen -- Remco -- Rex Lee(李俊) -- Richard Lee -- Riverside -- Robert Wilkinson -- Rogier Lommers -- Rohan Pai -- Romain Beuque -- Roman Belyakovsky -- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com> -- Roman Zaynetdinov -- Ronald Petty -- Ross Wolf <31489089+rw-access@users.noreply.github.com> -- Roy Lou -- Rubi <14269809+codenoid@users.noreply.github.com> -- Ryan <46182144+ryanker@users.noreply.github.com> -- Ryan J. Yoder -- SRK.Lyu -- Sai -- Samuel Abreu -- Santhosh Kumar -- Sasha Melentyev -- Sasha Myasoedov -- Segev Finer -- Sergey Egorov -- Sergey Fedchenko -- Sergey Gonimar -- Sergey Ponomarev -- Serica <943914044@qq.com> -- Shamus Taylor -- Shilin Wang -- Shuo -- Skuli Oskarsson -- Snawoot -- Sridhar Ratnakumar -- Steeve Chailloux -- Sudhir Mishra -- Suhas Karanth -- TaeJun Park -- Tatsuya Hoshino -- Tevic -- Tevin Jeffrey -- The Gitter Badger -- Thibault Jamet -- Thomas Boerger -- Thomas Schaffer -- Tommy Chu -- Tudor Roman -- Uwe Dauernheim -- Valentine Oragbakosi -- Vas N -- Vasilyuk Vasiliy -- Victor Castell -- Vince Yuan -- Vyacheslav Dubinin -- Waynerv -- Weilin Shi <934587911@qq.com> -- Xudong Cai -- Yasuhiro Matsumoto -- Yehezkiel Syamsuhadi -- Yoshiki Nakagawa -- Yoshiyuki Kinjo -- Yue Yang -- ZYunH -- Zach Newburgh -- Zasda Yusuf Mikail -- ZhangYunHao -- ZhiFeng Hu -- Zhu Xi -- a2tt -- ahuigo <1781999+ahuigo@users.noreply.github.com> -- ali -- aljun -- andrea -- andriikushch -- anoty -- awkj -- axiaoxin <254606826@qq.com> -- bbiao -- bestgopher <84328409@qq.com> -- betahu -- bigwheel -- bn4t <17193640+bn4t@users.noreply.github.com> -- bullgare -- chainhelen -- chenyang929 -- chriswhelix -- collinmsn <4130944@qq.com> -- cssivision -- danielalves -- delphinus -- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> -- dickeyxxx -- edebernis -- error10 -- esplo -- eudore <30709860+eudore@users.noreply.github.com> -- ffhelicopter <32922889+ffhelicopter@users.noreply.github.com> -- filikos <11477309+filikos@users.noreply.github.com> -- forging2012 -- goqihoo -- grapeVine -- guonaihong -- heige -- heige -- hellojukay -- henrylee2cn -- htobenothing -- iamhesir <78344375+iamhesir@users.noreply.github.com> -- ijaa -- ishanray -- ishanray -- itcloudy <272685110@qq.com> -- jarodsong6 -- jasonrhansen -- jincheng9 -- joeADSP <75027008+joeADSP@users.noreply.github.com> -- junfengye -- kaiiak -- kebo -- keke <19yamashita15@gmail.com> -- kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com> -- kyledinh -- lantw44 -- likakuli <1154584512@qq.com> -- linfangrong -- linzi <873804682@qq.com> -- llgoer -- long-road <13412081338@163.com> -- mbesancon -- mehdy -- metal A-wing -- micanzhang -- minarc -- mllu -- mopemoepe -- msoedov -- mstmdev -- novaeye -- olebedev -- phithon -- pjgg -- qm012 <67568757+qm012@users.noreply.github.com> -- raymonder jin -- rns -- root@andrea:~# -- sekky0905 <20237968+sekky0905@users.noreply.github.com> -- senhtry -- shadrus -- silasb -- solos -- songjiayang -- sope -- srt180 <30768686+srt180@users.noreply.github.com> -- stackerzzq -- sunshineplan -- syssam -- techjanitor -- techjanitor -- thinkerou -- thinkgo <49174849+thinkgos@users.noreply.github.com> -- tsirolnik -- tyltr <31768692+tylitianrui@users.noreply.github.com> -- vinhha96 -- voidman -- vz -- wei -- weibaohui -- whirosan -- willnewrelic -- wssccc -- wuhuizuo -- xyb -- y-yagi -- yiranzai -- youzeliang -- yugu -- yuyabe -- zebozhuang -- zero11-0203 <93071220+zero11-0203@users.noreply.github.com> -- zesani <7sin@outlook.co.th> -- zhanweidu -- zhing -- ziheng -- zzjin -- 森 優太 <59682979+uta-mori@users.noreply.github.com> -- 杰哥 <858806258@qq.com> -- 涛叔 -- 市民233 -- 尹宝强 -- 梦溪笔谈 -- 飞雪无情 -- 寻寻觅觅的Gopher diff --git a/BENCHMARKS.md b/BENCHMARKS.md index c11ee99a..111e04ad 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,666 +1,291 @@ +# Gin Benchmark Report -# Benchmark System - -**VM HOST:** Travis -**Machine:** Ubuntu 16.04.6 LTS x64 -**Date:** May 04th, 2020 -**Version:** Gin v1.6.3 -**Go Version:** 1.14.2 linux/amd64 +**Machine:** Apple M4 Pro +**OS:** macOS (Darwin 25.3.0), arm64 +**Date:** March 15th, 2026 +**Gin Version:** v1.12.0 +**Go Version:** 1.25.8 darwin/arm64 **Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark) -**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061) -## Static Routes: 157 +--- -```sh -Gin: 34936 Bytes +## Table of Contents -HttpServeMux: 14512 Bytes -Ace: 30680 Bytes -Aero: 34536 Bytes -Bear: 30456 Bytes -Beego: 98456 Bytes -Bone: 40224 Bytes -Chi: 83608 Bytes -Denco: 10216 Bytes -Echo: 80328 Bytes -GocraftWeb: 55288 Bytes -Goji: 29744 Bytes -Gojiv2: 105840 Bytes -GoJsonRest: 137496 Bytes -GoRestful: 816936 Bytes -GorillaMux: 585632 Bytes -GowwwRouter: 24968 Bytes -HttpRouter: 21712 Bytes -HttpTreeMux: 73448 Bytes -Kocha: 115472 Bytes -LARS: 30640 Bytes -Macaron: 38592 Bytes -Martini: 310864 Bytes -Pat: 19696 Bytes -Possum: 89920 Bytes -R2router: 23712 Bytes -Rivet: 24608 Bytes -Tango: 28264 Bytes -TigerTonic: 78768 Bytes -Traffic: 538976 Bytes -Vulcan: 369960 Bytes -``` +- [Summary](#summary) +- [Memory Consumption](#memory-consumption) +- [Benchmark Results](#benchmark-results) + - [GitHub API (203 routes)](#github-api-203-routes) + - [Google+ API (13 routes)](#google-api-13-routes) + - [Parse API (26 routes)](#parse-api-26-routes) + - [Static Routes (157 routes)](#static-routes-157-routes) +- [Micro Benchmarks](#micro-benchmarks) + - [Single Param](#single-param) + - [5 Params](#5-params) + - [20 Params](#20-params) + - [Param Write](#param-write) -## GithubAPI Routes: 203 +--- -```sh -Gin: 58512 Bytes +## Summary -Ace: 48688 Bytes -Aero: 318568 Bytes -Bear: 84248 Bytes -Beego: 150936 Bytes -Bone: 100976 Bytes -Chi: 95112 Bytes -Denco: 36736 Bytes -Echo: 100296 Bytes -GocraftWeb: 95432 Bytes -Goji: 49680 Bytes -Gojiv2: 104704 Bytes -GoJsonRest: 141976 Bytes -GoRestful: 1241656 Bytes -GorillaMux: 1322784 Bytes -GowwwRouter: 80008 Bytes -HttpRouter: 37144 Bytes -HttpTreeMux: 78800 Bytes -Kocha: 785120 Bytes -LARS: 48600 Bytes -Macaron: 92784 Bytes -Martini: 485264 Bytes -Pat: 21200 Bytes -Possum: 85312 Bytes -R2router: 47104 Bytes -Rivet: 42840 Bytes -Tango: 54840 Bytes -TigerTonic: 95264 Bytes -Traffic: 921744 Bytes -Vulcan: 425992 Bytes -``` +The table below ranks all routers by **GitHub API throughput** (203 routes, all methods), which best represents real-world routing workloads. _Lower ns/op is better._ -## GPlusAPI Routes: 13 +| Rank | Router | ns/op | B/op | allocs/op | Zero-alloc | +| :--: | :------------ | --------: | --------: | --------: | :----------------: | +| 1 | **Gin** | 9,944 | 0 | 0 | :white_check_mark: | +| 2 | **BunRouter** | 10,281 | 0 | 0 | :white_check_mark: | +| 3 | **Echo** | 11,072 | 0 | 0 | :white_check_mark: | +| 4 | HttpRouter | 15,059 | 13,792 | 167 | | +| 5 | HttpTreeMux | 49,302 | 65,856 | 671 | | +| 6 | Chi | 94,376 | 130,817 | 740 | | +| 7 | Beego | 101,941 | 71,456 | 609 | | +| 8 | Fiber | 109,148 | 0 | 0 | :white_check_mark: | +| 9 | Macaron | 121,785 | 147,784 | 1,624 | | +| 10 | Goji v2 | 242,849 | 313,744 | 3,712 | | +| 11 | GoRestful | 885,678 | 1,006,744 | 3,009 | | +| 12 | GorillaMux | 1,316,844 | 225,667 | 1,588 | | -```sh -Gin: 4384 Bytes +**Key takeaways:** -Ace: 3712 Bytes -Aero: 26056 Bytes -Bear: 7112 Bytes -Beego: 10272 Bytes -Bone: 6688 Bytes -Chi: 8024 Bytes -Denco: 3264 Bytes -Echo: 9688 Bytes -GocraftWeb: 7496 Bytes -Goji: 3152 Bytes -Gojiv2: 7376 Bytes -GoJsonRest: 11400 Bytes -GoRestful: 74328 Bytes -GorillaMux: 66208 Bytes -GowwwRouter: 5744 Bytes -HttpRouter: 2808 Bytes -HttpTreeMux: 7440 Bytes -Kocha: 128880 Bytes -LARS: 3656 Bytes -Macaron: 8656 Bytes -Martini: 23920 Bytes -Pat: 1856 Bytes -Possum: 7248 Bytes -R2router: 3928 Bytes -Rivet: 3064 Bytes -Tango: 5168 Bytes -TigerTonic: 9408 Bytes -Traffic: 46400 Bytes -Vulcan: 25544 Bytes -``` +- **Gin**, **BunRouter**, and **Echo** form the top tier — all achieve zero heap allocations and route the full GitHub API in ~10 us. +- **HttpRouter** remains extremely fast but incurs 1 alloc per parameterized route (167 allocs for 203 routes). +- **Fiber** also achieves zero allocations, but its fasthttp-based benchmark infrastructure adds per-iteration reset overhead — do not compare its absolute ns/op directly with net/http routers. +- **GorillaMux** and **GoRestful** are feature-rich but orders of magnitude slower, making them less suitable for latency-sensitive applications. -## ParseAPI Routes: 26 +> **Fiber caveat:** Fiber benchmarks use `fasthttp.RequestCtx` with per-iteration Reset, which adds constant overhead not present in net/http benchmarks. Fiber-vs-Fiber comparisons are valid; cross-framework comparisons should be interpreted with care. -```sh -Gin: 7776 Bytes +--- -Ace: 6704 Bytes -Aero: 28488 Bytes -Bear: 12320 Bytes -Beego: 19280 Bytes -Bone: 11440 Bytes -Chi: 9744 Bytes -Denco: 4192 Bytes -Echo: 11664 Bytes -GocraftWeb: 12800 Bytes -Goji: 5680 Bytes -Gojiv2: 14464 Bytes -GoJsonRest: 14072 Bytes -GoRestful: 116264 Bytes -GorillaMux: 105880 Bytes -GowwwRouter: 9344 Bytes -HttpRouter: 5072 Bytes -HttpTreeMux: 7848 Bytes -Kocha: 181712 Bytes -LARS: 6632 Bytes -Macaron: 13648 Bytes -Martini: 45888 Bytes -Pat: 2560 Bytes -Possum: 9200 Bytes -R2router: 7056 Bytes -Rivet: 5680 Bytes -Tango: 8920 Bytes -TigerTonic: 9840 Bytes -Traffic: 79096 Bytes -Vulcan: 44504 Bytes -``` +## Memory Consumption -## Static Routes +Memory required for loading the routing structure (lower is better). Sorted by bytes ascending. -```sh -BenchmarkGin_StaticAll 62169 19319 ns/op 0 B/op 0 allocs/op +### Static Routes: 157 -BenchmarkAce_StaticAll 65428 18313 ns/op 0 B/op 0 allocs/op -BenchmarkAero_StaticAll 121132 9632 ns/op 0 B/op 0 allocs/op -BenchmarkHttpServeMux_StaticAll 52626 22758 ns/op 0 B/op 0 allocs/op -BenchmarkBeego_StaticAll 9962 179058 ns/op 55264 B/op 471 allocs/op -BenchmarkBear_StaticAll 14894 80966 ns/op 20272 B/op 469 allocs/op -BenchmarkBone_StaticAll 18718 64065 ns/op 0 B/op 0 allocs/op -BenchmarkChi_StaticAll 10000 149827 ns/op 67824 B/op 471 allocs/op -BenchmarkDenco_StaticAll 211393 5680 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_StaticAll 49341 24343 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_StaticAll 10000 126209 ns/op 46312 B/op 785 allocs/op -BenchmarkGoji_StaticAll 27956 43174 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_StaticAll 3430 370718 ns/op 205984 B/op 1570 allocs/op -BenchmarkGoJsonRest_StaticAll 9134 188888 ns/op 51653 B/op 1727 allocs/op -BenchmarkGoRestful_StaticAll 706 1703330 ns/op 613280 B/op 2053 allocs/op -BenchmarkGorillaMux_StaticAll 1268 924083 ns/op 153233 B/op 1413 allocs/op -BenchmarkGowwwRouter_StaticAll 63374 18935 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_StaticAll 109938 10902 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_StaticAll 109166 10861 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_StaticAll 92258 12992 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_StaticAll 65200 18387 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_StaticAll 5671 291501 ns/op 115553 B/op 1256 allocs/op -BenchmarkMartini_StaticAll 807 1460498 ns/op 125444 B/op 1717 allocs/op -BenchmarkPat_StaticAll 513 2342396 ns/op 602832 B/op 12559 allocs/op -BenchmarkPossum_StaticAll 10000 128270 ns/op 65312 B/op 471 allocs/op -BenchmarkR2router_StaticAll 16726 71760 ns/op 22608 B/op 628 allocs/op -BenchmarkRivet_StaticAll 41722 28723 ns/op 0 B/op 0 allocs/op -BenchmarkTango_StaticAll 7606 205082 ns/op 39209 B/op 1256 allocs/op -BenchmarkTigerTonic_StaticAll 26247 45806 ns/op 7376 B/op 157 allocs/op -BenchmarkTraffic_StaticAll 550 2284518 ns/op 754864 B/op 14601 allocs/op -BenchmarkVulcan_StaticAll 10000 131343 ns/op 15386 B/op 471 allocs/op -``` +| Router | Bytes | +| :------------- | ---------: | +| **HttpRouter** | **21,680** | +| **Gin** | **34,408** | +| **Macaron** | **36,976** | +| BunRouter | 51,232 | +| Fiber | 59,248 | +| HttpServeMux | 71,728 | +| HttpTreeMux | 73,448 | +| Chi | 83,160 | +| Echo | 91,976 | +| Beego | 98,824 | +| Goji v2 | 117,952 | +| GorillaMux | 599,496 | +| GoRestful | 819,704 | + +### GitHub API Routes: 203 + +| Router | Bytes | +| :-------------- | ---------: | +| **HttpRouter** | **37,072** | +| **Gin** | **58,840** | +| **HttpTreeMux** | **78,800** | +| Macaron | 90,632 | +| BunRouter | 93,776 | +| Chi | 94,888 | +| Echo | 117,784 | +| Goji v2 | 118,640 | +| Beego | 150,840 | +| Fiber | 163,832 | +| GoRestful | 1,270,848 | +| GorillaMux | 1,319,696 | + +### Google+ API Routes: 13 + +| Router | Bytes | +| :------------- | --------: | +| **HttpRouter** | **2,776** | +| **Gin** | **4,576** | +| **BunRouter** | **7,360** | +| HttpTreeMux | 7,440 | +| Chi | 8,008 | +| Goji v2 | 8,096 | +| Macaron | 8,672 | +| Beego | 10,256 | +| Fiber | 10,840 | +| Echo | 10,968 | +| GorillaMux | 68,000 | +| GoRestful | 72,536 | + +### Parse API Routes: 26 + +| Router | Bytes | +| :-------------- | --------: | +| **HttpRouter** | **5,024** | +| **Gin** | **7,896** | +| **HttpTreeMux** | **7,848** | +| BunRouter | 9,336 | +| Chi | 9,656 | +| Echo | 13,816 | +| Macaron | 13,704 | +| Fiber | 15,352 | +| Goji v2 | 16,064 | +| Beego | 19,256 | +| GorillaMux | 105,384 | +| GoRestful | 121,200 | + +--- + +## Benchmark Results + +### GitHub API (203 routes) + +Routing all 203 GitHub API endpoints per operation. + +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | --------: | --------: | --------: | +| 1 | **Gin** | 9,944 | 0 | 0 | +| 2 | **BunRouter** | 10,281 | 0 | 0 | +| 3 | **Echo** | 11,072 | 0 | 0 | +| 4 | HttpRouter | 15,059 | 13,792 | 167 | +| 5 | HttpTreeMux | 49,302 | 65,856 | 671 | +| 6 | Chi | 94,376 | 130,817 | 740 | +| 7 | Beego | 101,941 | 71,456 | 609 | +| 8 | Fiber | 109,148 | 0 | 0 | +| 9 | Macaron | 121,785 | 147,784 | 1,624 | +| 10 | Goji v2 | 242,849 | 313,744 | 3,712 | +| 11 | GoRestful | 885,678 | 1,006,744 | 3,009 | +| 12 | GorillaMux | 1,316,844 | 225,667 | 1,588 | + +### Google+ API (13 routes) + +Routing all 13 Google+ API endpoints per operation. + +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | -----: | -----: | --------: | +| 1 | **BunRouter** | 348.5 | 0 | 0 | +| 2 | **Gin** | 429.7 | 0 | 0 | +| 3 | **Echo** | 451.1 | 0 | 0 | +| 4 | HttpRouter | 668.6 | 640 | 11 | +| 5 | HttpTreeMux | 2,428 | 4,032 | 38 | +| 6 | Fiber | 2,506 | 0 | 0 | +| 7 | Chi | 5,333 | 8,480 | 48 | +| 8 | Beego | 5,927 | 4,576 | 39 | +| 9 | Macaron | 7,294 | 9,464 | 104 | +| 10 | Goji v2 | 8,000 | 15,120 | 115 | +| 11 | GorillaMux | 14,707 | 14,448 | 102 | +| 12 | GoRestful | 24,189 | 60,720 | 193 | + +### Parse API (26 routes) + +Routing all 26 Parse API endpoints per operation. + +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | -----: | ------: | --------: | +| 1 | **BunRouter** | 588.2 | 0 | 0 | +| 2 | **Gin** | 712.1 | 0 | 0 | +| 3 | **Echo** | 742.1 | 0 | 0 | +| 4 | HttpRouter | 948.5 | 640 | 16 | +| 5 | HttpTreeMux | 3,372 | 5,728 | 51 | +| 6 | Fiber | 4,250 | 0 | 0 | +| 7 | Chi | 8,863 | 14,944 | 84 | +| 8 | Beego | 10,541 | 9,152 | 78 | +| 9 | Macaron | 13,635 | 18,928 | 208 | +| 10 | Goji v2 | 13,264 | 29,456 | 199 | +| 11 | GorillaMux | 25,886 | 26,960 | 198 | +| 12 | GoRestful | 54,780 | 131,728 | 380 | + +### Static Routes (157 routes) + +Routing all 157 static routes per operation. Includes http.ServeMux as baseline. + +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :-------------- | ------: | ------: | --------: | +| 1 | **HttpRouter** | 4,177 | 0 | 0 | +| 2 | **HttpTreeMux** | 5,363 | 0 | 0 | +| 3 | **Gin** | 5,528 | 0 | 0 | +| 4 | BunRouter | 5,997 | 0 | 0 | +| 5 | Echo | 6,897 | 0 | 0 | +| — | HttpServeMux | 18,172 | 0 | 0 | +| 6 | Fiber | 29,310 | 0 | 0 | +| 7 | Chi | 41,317 | 57,776 | 314 | +| 8 | Beego | 68,255 | 55,264 | 471 | +| 9 | Macaron | 81,824 | 114,296 | 1,256 | +| 10 | Goji v2 | 84,459 | 175,840 | 1,099 | +| 11 | GorillaMux | 302,825 | 133,137 | 1,099 | +| 12 | GoRestful | 436,510 | 677,824 | 2,193 | + +--- ## Micro Benchmarks -```sh -BenchmarkGin_Param 18785022 63.9 ns/op 0 B/op 0 allocs/op +### Single Param -BenchmarkAce_Param 14689765 81.5 ns/op 0 B/op 0 allocs/op -BenchmarkAero_Param 23094770 51.2 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param 1417045 845 ns/op 456 B/op 5 allocs/op -BenchmarkBeego_Param 1000000 1080 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param 1000000 1463 ns/op 816 B/op 6 allocs/op -BenchmarkChi_Param 1378756 885 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_Param 8557899 143 ns/op 32 B/op 1 allocs/op -BenchmarkEcho_Param 16433347 75.5 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param 1000000 1218 ns/op 648 B/op 8 allocs/op -BenchmarkGoji_Param 1921248 617 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Param 561848 2156 ns/op 1328 B/op 11 allocs/op -BenchmarkGoJsonRest_Param 1000000 1358 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_Param 224857 5307 ns/op 4192 B/op 14 allocs/op -BenchmarkGorillaMux_Param 498313 2459 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_Param 1864354 654 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param 26269074 47.7 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_Param 2109829 557 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_Param 5050216 243 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_Param 19811712 59.9 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param 662746 2329 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Param 279902 4260 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_Param 1000000 1382 ns/op 536 B/op 11 allocs/op -BenchmarkPossum_Param 1000000 1014 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param 1712559 707 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Param 6648086 182 ns/op 48 B/op 1 allocs/op -BenchmarkTango_Param 1221504 994 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_Param 891661 2261 ns/op 776 B/op 16 allocs/op -BenchmarkTraffic_Param 350059 3598 ns/op 1856 B/op 21 allocs/op -BenchmarkVulcan_Param 2517823 472 ns/op 98 B/op 3 allocs/op -BenchmarkAce_Param5 9214365 130 ns/op 0 B/op 0 allocs/op -BenchmarkAero_Param5 15369013 77.9 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param5 1000000 1113 ns/op 501 B/op 5 allocs/op -BenchmarkBeego_Param5 1000000 1269 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param5 986820 1873 ns/op 864 B/op 6 allocs/op -BenchmarkChi_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_Param5 3036331 400 ns/op 160 B/op 1 allocs/op -BenchmarkEcho_Param5 6447133 186 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Param5 10786068 110 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param5 844820 1944 ns/op 920 B/op 11 allocs/op -BenchmarkGoji_Param5 1474965 827 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Param5 442820 2516 ns/op 1392 B/op 11 allocs/op -BenchmarkGoJsonRest_Param5 507555 2711 ns/op 1097 B/op 16 allocs/op -BenchmarkGoRestful_Param5 216481 6093 ns/op 4288 B/op 14 allocs/op -BenchmarkGorillaMux_Param5 314402 3628 ns/op 1344 B/op 10 allocs/op -BenchmarkGowwwRouter_Param5 1624660 733 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param5 13167324 92.0 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_Param5 1000000 1295 ns/op 576 B/op 6 allocs/op -BenchmarkKocha_Param5 1000000 1138 ns/op 440 B/op 10 allocs/op -BenchmarkLARS_Param5 11580613 105 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param5 473596 2755 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Param5 230756 5111 ns/op 1232 B/op 11 allocs/op -BenchmarkPat_Param5 469190 3370 ns/op 888 B/op 29 allocs/op -BenchmarkPossum_Param5 1000000 1002 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param5 1422129 844 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Param5 2263789 539 ns/op 240 B/op 1 allocs/op -BenchmarkTango_Param5 1000000 1256 ns/op 360 B/op 8 allocs/op -BenchmarkTigerTonic_Param5 175500 7492 ns/op 2279 B/op 39 allocs/op -BenchmarkTraffic_Param5 233631 5816 ns/op 2208 B/op 27 allocs/op -BenchmarkVulcan_Param5 1923416 629 ns/op 98 B/op 3 allocs/op -BenchmarkAce_Param20 4321266 281 ns/op 0 B/op 0 allocs/op -BenchmarkAero_Param20 31501641 35.2 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param20 335204 3489 ns/op 1665 B/op 5 allocs/op -BenchmarkBeego_Param20 503674 2860 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param20 298922 4741 ns/op 2031 B/op 6 allocs/op -BenchmarkChi_Param20 878181 1957 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_Param20 1000000 1360 ns/op 640 B/op 1 allocs/op -BenchmarkEcho_Param20 2104946 580 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Param20 4167204 290 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param20 173064 7514 ns/op 3796 B/op 15 allocs/op -BenchmarkGoji_Param20 458778 2651 ns/op 1247 B/op 2 allocs/op -BenchmarkGojiv2_Param20 364862 3178 ns/op 1632 B/op 11 allocs/op -BenchmarkGoJsonRest_Param20 125514 9760 ns/op 4485 B/op 20 allocs/op -BenchmarkGoRestful_Param20 101217 11964 ns/op 6715 B/op 18 allocs/op -BenchmarkGorillaMux_Param20 147654 8132 ns/op 3452 B/op 12 allocs/op -BenchmarkGowwwRouter_Param20 1000000 1225 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param20 4920895 247 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_Param20 173202 6605 ns/op 3196 B/op 10 allocs/op -BenchmarkKocha_Param20 345988 3620 ns/op 1808 B/op 27 allocs/op -BenchmarkLARS_Param20 4592326 262 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param20 166492 7286 ns/op 2924 B/op 12 allocs/op -BenchmarkMartini_Param20 122162 10653 ns/op 3595 B/op 13 allocs/op -BenchmarkPat_Param20 78630 15239 ns/op 4424 B/op 93 allocs/op -BenchmarkPossum_Param20 1000000 1008 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param20 294981 4587 ns/op 2284 B/op 7 allocs/op -BenchmarkRivet_Param20 691798 2090 ns/op 1024 B/op 1 allocs/op -BenchmarkTango_Param20 842440 2505 ns/op 856 B/op 8 allocs/op -BenchmarkTigerTonic_Param20 38614 31509 ns/op 9870 B/op 119 allocs/op -BenchmarkTraffic_Param20 57633 21107 ns/op 7853 B/op 47 allocs/op -BenchmarkVulcan_Param20 1000000 1178 ns/op 98 B/op 3 allocs/op -BenchmarkAce_ParamWrite 7330743 180 ns/op 8 B/op 1 allocs/op -BenchmarkAero_ParamWrite 13833598 86.7 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParamWrite 1363321 867 ns/op 456 B/op 5 allocs/op -BenchmarkBeego_ParamWrite 1000000 1104 ns/op 360 B/op 4 allocs/op -BenchmarkBone_ParamWrite 1000000 1475 ns/op 816 B/op 6 allocs/op -BenchmarkChi_ParamWrite 1320590 892 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_ParamWrite 7093605 172 ns/op 32 B/op 1 allocs/op -BenchmarkEcho_ParamWrite 8434424 161 ns/op 8 B/op 1 allocs/op -BenchmarkGin_ParamWrite 10377034 118 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParamWrite 1000000 1266 ns/op 656 B/op 9 allocs/op -BenchmarkGoji_ParamWrite 1874168 654 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_ParamWrite 459032 2352 ns/op 1360 B/op 13 allocs/op -BenchmarkGoJsonRest_ParamWrite 499434 2145 ns/op 1128 B/op 18 allocs/op -BenchmarkGoRestful_ParamWrite 241087 5470 ns/op 4200 B/op 15 allocs/op -BenchmarkGorillaMux_ParamWrite 425686 2522 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_ParamWrite 922172 1778 ns/op 976 B/op 8 allocs/op -BenchmarkHttpRouter_ParamWrite 15392049 77.7 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_ParamWrite 1973385 597 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_ParamWrite 4262500 281 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_ParamWrite 10764410 113 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParamWrite 486769 2726 ns/op 1176 B/op 14 allocs/op -BenchmarkMartini_ParamWrite 264804 4842 ns/op 1176 B/op 14 allocs/op -BenchmarkPat_ParamWrite 735116 2047 ns/op 960 B/op 15 allocs/op -BenchmarkPossum_ParamWrite 1000000 1004 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_ParamWrite 1592136 768 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_ParamWrite 3582051 339 ns/op 112 B/op 2 allocs/op -BenchmarkTango_ParamWrite 2237337 534 ns/op 136 B/op 4 allocs/op -BenchmarkTigerTonic_ParamWrite 439608 3136 ns/op 1216 B/op 21 allocs/op -BenchmarkTraffic_ParamWrite 306979 4328 ns/op 2280 B/op 25 allocs/op -BenchmarkVulcan_ParamWrite 2529973 472 ns/op 98 B/op 3 allocs/op -``` +Route: `/user/:name` — Request: `GET /user/gordon` -## GitHub +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | ----: | ----: | --------: | +| 1 | **BunRouter** | 12.22 | 0 | 0 | +| 2 | **Echo** | 17.75 | 0 | 0 | +| 3 | **Gin** | 23.31 | 0 | 0 | +| 4 | HttpRouter | 31.88 | 32 | 1 | +| 5 | Fiber | 114.4 | 0 | 0 | +| 6 | HttpTreeMux | 165.0 | 352 | 3 | +| 7 | Chi | 332.2 | 704 | 4 | +| 8 | Beego | 348.8 | 352 | 3 | +| 9 | Goji v2 | 494.3 | 1,136 | 8 | +| 10 | GorillaMux | 630.6 | 1,152 | 8 | +| 11 | Macaron | 708.0 | 1,064 | 10 | +| 12 | GoRestful | 1,394 | 4,600 | 15 | -```sh -BenchmarkGin_GithubStatic 15629472 76.7 ns/op 0 B/op 0 allocs/op +### 5 Params -BenchmarkAce_GithubStatic 15542612 75.9 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GithubStatic 24777151 48.5 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubStatic 2788894 435 ns/op 120 B/op 3 allocs/op -BenchmarkBeego_GithubStatic 1000000 1064 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GithubStatic 93507 12838 ns/op 2880 B/op 60 allocs/op -BenchmarkChi_GithubStatic 1387743 860 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GithubStatic 39384996 30.4 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_GithubStatic 12076382 99.1 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubStatic 1596495 756 ns/op 296 B/op 5 allocs/op -BenchmarkGoji_GithubStatic 6364876 189 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_GithubStatic 550202 2098 ns/op 1312 B/op 10 allocs/op -BenchmarkGoRestful_GithubStatic 102183 12552 ns/op 4256 B/op 13 allocs/op -BenchmarkGoJsonRest_GithubStatic 1000000 1029 ns/op 329 B/op 11 allocs/op -BenchmarkGorillaMux_GithubStatic 255552 5190 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_GithubStatic 15531916 77.1 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_GithubStatic 27920724 43.1 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GithubStatic 21448953 55.8 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_GithubStatic 21405310 56.0 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_GithubStatic 13625156 89.0 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubStatic 1000000 1747 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_GithubStatic 187186 7326 ns/op 768 B/op 9 allocs/op -BenchmarkPat_GithubStatic 109143 11563 ns/op 3648 B/op 76 allocs/op -BenchmarkPossum_GithubStatic 1575898 770 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_GithubStatic 3046231 404 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_GithubStatic 11484826 105 ns/op 0 B/op 0 allocs/op -BenchmarkTango_GithubStatic 1000000 1153 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_GithubStatic 4929780 249 ns/op 48 B/op 1 allocs/op -BenchmarkTraffic_GithubStatic 106351 11819 ns/op 4664 B/op 90 allocs/op -BenchmarkVulcan_GithubStatic 1613271 722 ns/op 98 B/op 3 allocs/op -BenchmarkAce_GithubParam 8386032 143 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GithubParam 11816200 102 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubParam 1000000 1012 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_GithubParam 1000000 1157 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GithubParam 184653 6912 ns/op 1888 B/op 19 allocs/op -BenchmarkChi_GithubParam 1000000 1102 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GithubParam 3484798 352 ns/op 128 B/op 1 allocs/op -BenchmarkEcho_GithubParam 6337380 189 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GithubParam 9132032 131 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubParam 1000000 1446 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_GithubParam 1248640 977 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GithubParam 383233 2784 ns/op 1408 B/op 13 allocs/op -BenchmarkGoJsonRest_GithubParam 1000000 1991 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_GithubParam 76414 16015 ns/op 4352 B/op 16 allocs/op -BenchmarkGorillaMux_GithubParam 150026 7663 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_GithubParam 1592044 751 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GithubParam 10420628 115 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GithubParam 1403755 835 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_GithubParam 2286170 533 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_GithubParam 9540374 129 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubParam 533154 2742 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GithubParam 119397 9638 ns/op 1152 B/op 11 allocs/op -BenchmarkPat_GithubParam 150675 8858 ns/op 2408 B/op 48 allocs/op -BenchmarkPossum_GithubParam 1000000 1001 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GithubParam 1602886 761 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GithubParam 2986579 409 ns/op 96 B/op 1 allocs/op -BenchmarkTango_GithubParam 1000000 1356 ns/op 344 B/op 8 allocs/op -BenchmarkTigerTonic_GithubParam 388899 3429 ns/op 1176 B/op 22 allocs/op -BenchmarkTraffic_GithubParam 123160 9734 ns/op 2816 B/op 40 allocs/op -BenchmarkVulcan_GithubParam 1000000 1138 ns/op 98 B/op 3 allocs/op -BenchmarkAce_GithubAll 40543 29670 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GithubAll 57632 20648 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubAll 9234 216179 ns/op 86448 B/op 943 allocs/op -BenchmarkBeego_GithubAll 7407 243496 ns/op 71456 B/op 609 allocs/op -BenchmarkBone_GithubAll 420 2922835 ns/op 720160 B/op 8620 allocs/op -BenchmarkChi_GithubAll 7620 238331 ns/op 87696 B/op 609 allocs/op -BenchmarkDenco_GithubAll 18355 64494 ns/op 20224 B/op 167 allocs/op -BenchmarkEcho_GithubAll 31251 38479 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GithubAll 43550 27364 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubAll 4117 300062 ns/op 131656 B/op 1686 allocs/op -BenchmarkGoji_GithubAll 3274 416158 ns/op 56112 B/op 334 allocs/op -BenchmarkGojiv2_GithubAll 1402 870518 ns/op 352720 B/op 4321 allocs/op -BenchmarkGoJsonRest_GithubAll 2976 401507 ns/op 134371 B/op 2737 allocs/op -BenchmarkGoRestful_GithubAll 410 2913158 ns/op 910144 B/op 2938 allocs/op -BenchmarkGorillaMux_GithubAll 346 3384987 ns/op 251650 B/op 1994 allocs/op -BenchmarkGowwwRouter_GithubAll 10000 143025 ns/op 72144 B/op 501 allocs/op -BenchmarkHttpRouter_GithubAll 55938 21360 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GithubAll 10000 153944 ns/op 65856 B/op 671 allocs/op -BenchmarkKocha_GithubAll 10000 106315 ns/op 23304 B/op 843 allocs/op -BenchmarkLARS_GithubAll 47779 25084 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubAll 3266 371907 ns/op 149409 B/op 1624 allocs/op -BenchmarkMartini_GithubAll 331 3444706 ns/op 226551 B/op 2325 allocs/op -BenchmarkPat_GithubAll 273 4381818 ns/op 1483152 B/op 26963 allocs/op -BenchmarkPossum_GithubAll 10000 164367 ns/op 84448 B/op 609 allocs/op -BenchmarkR2router_GithubAll 10000 160220 ns/op 77328 B/op 979 allocs/op -BenchmarkRivet_GithubAll 14625 82453 ns/op 16272 B/op 167 allocs/op -BenchmarkTango_GithubAll 6255 279611 ns/op 63826 B/op 1618 allocs/op -BenchmarkTigerTonic_GithubAll 2008 687874 ns/op 193856 B/op 4474 allocs/op -BenchmarkTraffic_GithubAll 355 3478508 ns/op 820744 B/op 14114 allocs/op -BenchmarkVulcan_GithubAll 6885 193333 ns/op 19894 B/op 609 allocs/op -``` +Route: `/:a/:b/:c/:d/:e` — Request: `GET /test/test/test/test/test` -## Google+ +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | ----: | ----: | --------: | +| 1 | **BunRouter** | 41.86 | 0 | 0 | +| 2 | **Echo** | 43.76 | 0 | 0 | +| 3 | **Gin** | 44.20 | 0 | 0 | +| 4 | HttpRouter | 83.74 | 160 | 1 | +| 5 | Fiber | 271.6 | 0 | 0 | +| 6 | HttpTreeMux | 358.8 | 576 | 6 | +| 7 | Chi | 453.7 | 704 | 4 | +| 8 | Beego | 480.3 | 352 | 3 | +| 9 | Goji v2 | 532.4 | 1,200 | 8 | +| 10 | Macaron | 799.7 | 1,064 | 10 | +| 11 | GorillaMux | 972.6 | 1,216 | 8 | +| 12 | GoRestful | 1,579 | 4,712 | 15 | -```sh -BenchmarkGin_GPlusStatic 19247326 62.2 ns/op 0 B/op 0 allocs/op +### 20 Params -BenchmarkAce_GPlusStatic 20235060 59.2 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GPlusStatic 31978935 37.6 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusStatic 3516523 341 ns/op 104 B/op 3 allocs/op -BenchmarkBeego_GPlusStatic 1212036 991 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlusStatic 6736242 183 ns/op 32 B/op 1 allocs/op -BenchmarkChi_GPlusStatic 1490640 814 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GPlusStatic 55006856 21.8 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_GPlusStatic 17688258 67.9 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusStatic 1829181 666 ns/op 280 B/op 5 allocs/op -BenchmarkGoji_GPlusStatic 9147451 130 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_GPlusStatic 594015 2063 ns/op 1312 B/op 10 allocs/op -BenchmarkGoJsonRest_GPlusStatic 1264906 950 ns/op 329 B/op 11 allocs/op -BenchmarkGoRestful_GPlusStatic 231558 5341 ns/op 3872 B/op 13 allocs/op -BenchmarkGorillaMux_GPlusStatic 908418 1809 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_GPlusStatic 40684604 29.5 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_GPlusStatic 46742804 25.7 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GPlusStatic 32567161 36.9 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_GPlusStatic 33800060 35.3 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_GPlusStatic 20431858 60.0 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusStatic 1000000 1745 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_GPlusStatic 442248 3619 ns/op 768 B/op 9 allocs/op -BenchmarkPat_GPlusStatic 4328004 292 ns/op 96 B/op 2 allocs/op -BenchmarkPossum_GPlusStatic 1570753 763 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_GPlusStatic 3339474 355 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_GPlusStatic 18570961 64.7 ns/op 0 B/op 0 allocs/op -BenchmarkTango_GPlusStatic 1388702 860 ns/op 200 B/op 8 allocs/op -BenchmarkTigerTonic_GPlusStatic 7803543 159 ns/op 32 B/op 1 allocs/op -BenchmarkTraffic_GPlusStatic 878605 2171 ns/op 1112 B/op 16 allocs/op -BenchmarkVulcan_GPlusStatic 2742446 437 ns/op 98 B/op 3 allocs/op -BenchmarkAce_GPlusParam 11626975 105 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GPlusParam 16914322 71.6 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusParam 1405173 832 ns/op 480 B/op 5 allocs/op -BenchmarkBeego_GPlusParam 1000000 1075 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlusParam 1000000 1557 ns/op 816 B/op 6 allocs/op -BenchmarkChi_GPlusParam 1347926 894 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GPlusParam 5513000 212 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_GPlusParam 11884383 101 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlusParam 12898952 93.1 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusParam 1000000 1194 ns/op 648 B/op 8 allocs/op -BenchmarkGoji_GPlusParam 1857229 645 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GPlusParam 520939 2322 ns/op 1328 B/op 11 allocs/op -BenchmarkGoJsonRest_GPlusParam 1000000 1536 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_GPlusParam 205449 5800 ns/op 4192 B/op 14 allocs/op -BenchmarkGorillaMux_GPlusParam 395310 3188 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_GPlusParam 1851798 667 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GPlusParam 18420789 65.2 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GPlusParam 1878463 629 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_GPlusParam 4495610 273 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_GPlusParam 14615976 83.2 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusParam 584145 2549 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GPlusParam 250501 4583 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_GPlusParam 1000000 1645 ns/op 576 B/op 11 allocs/op -BenchmarkPossum_GPlusParam 1000000 1008 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GPlusParam 1708191 688 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GPlusParam 5795014 211 ns/op 48 B/op 1 allocs/op -BenchmarkTango_GPlusParam 1000000 1091 ns/op 264 B/op 8 allocs/op -BenchmarkTigerTonic_GPlusParam 760221 2489 ns/op 856 B/op 16 allocs/op -BenchmarkTraffic_GPlusParam 309774 4039 ns/op 1872 B/op 21 allocs/op -BenchmarkVulcan_GPlusParam 1935730 623 ns/op 98 B/op 3 allocs/op -BenchmarkAce_GPlus2Params 9158314 134 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GPlus2Params 11300517 107 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlus2Params 1239238 961 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_GPlus2Params 1000000 1202 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlus2Params 335576 3725 ns/op 1168 B/op 10 allocs/op -BenchmarkChi_GPlus2Params 1000000 1014 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GPlus2Params 4394598 280 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_GPlus2Params 7851861 154 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlus2Params 9958588 120 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlus2Params 1000000 1433 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_GPlus2Params 1325134 909 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GPlus2Params 405955 2870 ns/op 1408 B/op 14 allocs/op -BenchmarkGoJsonRest_GPlus2Params 977038 1987 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_GPlus2Params 205018 6142 ns/op 4384 B/op 16 allocs/op -BenchmarkGorillaMux_GPlus2Params 205641 6015 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_GPlus2Params 1748542 684 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GPlus2Params 14047102 87.7 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GPlus2Params 1418673 828 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_GPlus2Params 2334562 520 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_GPlus2Params 11954094 101 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlus2Params 491552 2890 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GPlus2Params 120532 9545 ns/op 1200 B/op 13 allocs/op -BenchmarkPat_GPlus2Params 194739 6766 ns/op 2168 B/op 33 allocs/op -BenchmarkPossum_GPlus2Params 1201224 1009 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GPlus2Params 1575535 756 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GPlus2Params 3698930 325 ns/op 96 B/op 1 allocs/op -BenchmarkTango_GPlus2Params 1000000 1212 ns/op 344 B/op 8 allocs/op -BenchmarkTigerTonic_GPlus2Params 349350 3660 ns/op 1200 B/op 22 allocs/op -BenchmarkTraffic_GPlus2Params 169714 7862 ns/op 2248 B/op 28 allocs/op -BenchmarkVulcan_GPlus2Params 1222288 974 ns/op 98 B/op 3 allocs/op -BenchmarkAce_GPlusAll 845606 1398 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GPlusAll 1000000 1009 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusAll 103830 11386 ns/op 5488 B/op 61 allocs/op -BenchmarkBeego_GPlusAll 82653 14784 ns/op 4576 B/op 39 allocs/op -BenchmarkBone_GPlusAll 36601 33123 ns/op 11744 B/op 109 allocs/op -BenchmarkChi_GPlusAll 95264 12831 ns/op 5616 B/op 39 allocs/op -BenchmarkDenco_GPlusAll 567681 2950 ns/op 672 B/op 11 allocs/op -BenchmarkEcho_GPlusAll 720366 1665 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlusAll 1000000 1185 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusAll 71575 16365 ns/op 8040 B/op 103 allocs/op -BenchmarkGoji_GPlusAll 136352 9191 ns/op 3696 B/op 22 allocs/op -BenchmarkGojiv2_GPlusAll 38006 31802 ns/op 17616 B/op 154 allocs/op -BenchmarkGoJsonRest_GPlusAll 57238 21561 ns/op 8117 B/op 170 allocs/op -BenchmarkGoRestful_GPlusAll 15147 79276 ns/op 55520 B/op 192 allocs/op -BenchmarkGorillaMux_GPlusAll 24446 48410 ns/op 16112 B/op 128 allocs/op -BenchmarkGowwwRouter_GPlusAll 150112 7770 ns/op 4752 B/op 33 allocs/op -BenchmarkHttpRouter_GPlusAll 1367820 878 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GPlusAll 166628 8004 ns/op 4032 B/op 38 allocs/op -BenchmarkKocha_GPlusAll 265694 4570 ns/op 976 B/op 43 allocs/op -BenchmarkLARS_GPlusAll 1000000 1068 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusAll 54564 23305 ns/op 9568 B/op 104 allocs/op -BenchmarkMartini_GPlusAll 16274 73845 ns/op 14016 B/op 145 allocs/op -BenchmarkPat_GPlusAll 27181 44478 ns/op 15264 B/op 271 allocs/op -BenchmarkPossum_GPlusAll 122587 10277 ns/op 5408 B/op 39 allocs/op -BenchmarkR2router_GPlusAll 130137 9297 ns/op 5040 B/op 63 allocs/op -BenchmarkRivet_GPlusAll 532438 3323 ns/op 768 B/op 11 allocs/op -BenchmarkTango_GPlusAll 86054 14531 ns/op 3656 B/op 104 allocs/op -BenchmarkTigerTonic_GPlusAll 33936 35356 ns/op 11600 B/op 242 allocs/op -BenchmarkTraffic_GPlusAll 17833 68181 ns/op 26248 B/op 341 allocs/op -BenchmarkVulcan_GPlusAll 120109 9861 ns/op 1274 B/op 39 allocs/op -``` +Route: `/:a/:b/.../:t` (20 segments) — Request: `GET /a/b/.../t` -## Parse.com +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | ----: | ----: | --------: | +| 1 | **Gin** | 121.7 | 0 | 0 | +| 2 | **Echo** | 127.5 | 0 | 0 | +| 3 | **BunRouter** | 211.4 | 0 | 0 | +| 4 | HttpRouter | 290.2 | 704 | 1 | +| 5 | Fiber | 466.1 | 0 | 0 | +| 6 | Goji v2 | 745.3 | 1,440 | 8 | +| 7 | Beego | 1,099 | 352 | 3 | +| 8 | Chi | 1,805 | 2,504 | 9 | +| 9 | HttpTreeMux | 1,857 | 3,144 | 13 | +| 10 | Macaron | 2,058 | 2,864 | 15 | +| 11 | GorillaMux | 2,223 | 3,272 | 13 | +| 12 | GoRestful | 3,337 | 7,008 | 20 | -```sh -BenchmarkGin_ParseStatic 18877833 63.5 ns/op 0 B/op 0 allocs/op +### Param Write -BenchmarkAce_ParseStatic 19663731 60.8 ns/op 0 B/op 0 allocs/op -BenchmarkAero_ParseStatic 28967341 41.5 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseStatic 3006984 402 ns/op 120 B/op 3 allocs/op -BenchmarkBeego_ParseStatic 1000000 1031 ns/op 352 B/op 3 allocs/op -BenchmarkBone_ParseStatic 1782482 675 ns/op 144 B/op 3 allocs/op -BenchmarkChi_ParseStatic 1453261 819 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_ParseStatic 45023595 26.5 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_ParseStatic 17330470 69.3 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseStatic 1644006 731 ns/op 296 B/op 5 allocs/op -BenchmarkGoji_ParseStatic 7026930 170 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_ParseStatic 517618 2037 ns/op 1312 B/op 10 allocs/op -BenchmarkGoJsonRest_ParseStatic 1227080 975 ns/op 329 B/op 11 allocs/op -BenchmarkGoRestful_ParseStatic 192458 6659 ns/op 4256 B/op 13 allocs/op -BenchmarkGorillaMux_ParseStatic 744062 2109 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_ParseStatic 37781062 31.8 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_ParseStatic 45311223 26.5 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_ParseStatic 21383475 56.1 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_ParseStatic 29953290 40.1 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_ParseStatic 20036196 62.7 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseStatic 1000000 1740 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_ParseStatic 404156 3801 ns/op 768 B/op 9 allocs/op -BenchmarkPat_ParseStatic 1547180 772 ns/op 240 B/op 5 allocs/op -BenchmarkPossum_ParseStatic 1608991 757 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_ParseStatic 3177936 385 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_ParseStatic 17783205 67.4 ns/op 0 B/op 0 allocs/op -BenchmarkTango_ParseStatic 1210777 990 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_ParseStatic 5316440 231 ns/op 48 B/op 1 allocs/op -BenchmarkTraffic_ParseStatic 496050 2539 ns/op 1256 B/op 19 allocs/op -BenchmarkVulcan_ParseStatic 2462798 488 ns/op 98 B/op 3 allocs/op -BenchmarkAce_ParseParam 13393669 89.6 ns/op 0 B/op 0 allocs/op -BenchmarkAero_ParseParam 19836619 60.4 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseParam 1405954 864 ns/op 467 B/op 5 allocs/op -BenchmarkBeego_ParseParam 1000000 1065 ns/op 352 B/op 3 allocs/op -BenchmarkBone_ParseParam 1000000 1698 ns/op 896 B/op 7 allocs/op -BenchmarkChi_ParseParam 1356037 873 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_ParseParam 6241392 204 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_ParseParam 14088100 85.1 ns/op 0 B/op 0 allocs/op -BenchmarkGin_ParseParam 17426064 68.9 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseParam 1000000 1254 ns/op 664 B/op 8 allocs/op -BenchmarkGoji_ParseParam 1682574 713 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_ParseParam 502224 2333 ns/op 1360 B/op 12 allocs/op -BenchmarkGoJsonRest_ParseParam 1000000 1401 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_ParseParam 182623 7097 ns/op 4576 B/op 14 allocs/op -BenchmarkGorillaMux_ParseParam 482332 2477 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_ParseParam 1834873 657 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_ParseParam 23593393 51.0 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_ParseParam 2100160 574 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_ParseParam 4837220 252 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_ParseParam 18411192 66.2 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseParam 571870 2398 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_ParseParam 286262 4268 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_ParseParam 692906 2157 ns/op 992 B/op 15 allocs/op -BenchmarkPossum_ParseParam 1000000 1011 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_ParseParam 1722735 697 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_ParseParam 6058054 203 ns/op 48 B/op 1 allocs/op -BenchmarkTango_ParseParam 1000000 1061 ns/op 280 B/op 8 allocs/op -BenchmarkTigerTonic_ParseParam 890275 2277 ns/op 784 B/op 15 allocs/op -BenchmarkTraffic_ParseParam 351322 3543 ns/op 1896 B/op 21 allocs/op -BenchmarkVulcan_ParseParam 2076544 572 ns/op 98 B/op 3 allocs/op -BenchmarkAce_Parse2Params 11718074 101 ns/op 0 B/op 0 allocs/op -BenchmarkAero_Parse2Params 16264988 73.4 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Parse2Params 1238322 973 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_Parse2Params 1000000 1120 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Parse2Params 1000000 1632 ns/op 848 B/op 6 allocs/op -BenchmarkChi_Parse2Params 1239477 955 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_Parse2Params 4944133 245 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_Parse2Params 10518286 114 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Parse2Params 14505195 82.7 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Parse2Params 1000000 1437 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_Parse2Params 1689883 707 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Parse2Params 502334 2308 ns/op 1344 B/op 11 allocs/op -BenchmarkGoJsonRest_Parse2Params 1000000 1771 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_Parse2Params 159092 7583 ns/op 4928 B/op 14 allocs/op -BenchmarkGorillaMux_Parse2Params 417548 2980 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_Parse2Params 1751737 686 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Parse2Params 18089204 66.3 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_Parse2Params 1556986 777 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_Parse2Params 2493082 485 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_Parse2Params 15350108 78.5 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Parse2Params 530974 2605 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Parse2Params 247069 4673 ns/op 1152 B/op 11 allocs/op -BenchmarkPat_Parse2Params 816295 2126 ns/op 752 B/op 16 allocs/op -BenchmarkPossum_Parse2Params 1000000 1002 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Parse2Params 1569771 733 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Parse2Params 4080546 295 ns/op 96 B/op 1 allocs/op -BenchmarkTango_Parse2Params 1000000 1121 ns/op 312 B/op 8 allocs/op -BenchmarkTigerTonic_Parse2Params 399556 3470 ns/op 1168 B/op 22 allocs/op -BenchmarkTraffic_Parse2Params 314194 4159 ns/op 1944 B/op 22 allocs/op -BenchmarkVulcan_Parse2Params 1827559 664 ns/op 98 B/op 3 allocs/op -BenchmarkAce_ParseAll 478395 2503 ns/op 0 B/op 0 allocs/op -BenchmarkAero_ParseAll 715392 1658 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseAll 59191 20124 ns/op 8928 B/op 110 allocs/op -BenchmarkBeego_ParseAll 45507 27266 ns/op 9152 B/op 78 allocs/op -BenchmarkBone_ParseAll 29328 41459 ns/op 16208 B/op 147 allocs/op -BenchmarkChi_ParseAll 48531 25053 ns/op 11232 B/op 78 allocs/op -BenchmarkDenco_ParseAll 325532 4284 ns/op 928 B/op 16 allocs/op -BenchmarkEcho_ParseAll 433771 2759 ns/op 0 B/op 0 allocs/op -BenchmarkGin_ParseAll 576316 2082 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseAll 41500 29692 ns/op 13728 B/op 181 allocs/op -BenchmarkGoji_ParseAll 80833 15563 ns/op 5376 B/op 32 allocs/op -BenchmarkGojiv2_ParseAll 19836 60335 ns/op 34448 B/op 277 allocs/op -BenchmarkGoJsonRest_ParseAll 32210 38027 ns/op 13866 B/op 321 allocs/op -BenchmarkGoRestful_ParseAll 6644 190842 ns/op 117600 B/op 354 allocs/op -BenchmarkGorillaMux_ParseAll 12634 95894 ns/op 30288 B/op 250 allocs/op -BenchmarkGowwwRouter_ParseAll 98152 12159 ns/op 6912 B/op 48 allocs/op -BenchmarkHttpRouter_ParseAll 933208 1273 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_ParseAll 107191 11554 ns/op 5728 B/op 51 allocs/op -BenchmarkKocha_ParseAll 184862 6225 ns/op 1112 B/op 54 allocs/op -BenchmarkLARS_ParseAll 644546 1858 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseAll 26145 46484 ns/op 19136 B/op 208 allocs/op -BenchmarkMartini_ParseAll 10000 121838 ns/op 25072 B/op 253 allocs/op -BenchmarkPat_ParseAll 25417 47196 ns/op 15216 B/op 308 allocs/op -BenchmarkPossum_ParseAll 58550 20735 ns/op 10816 B/op 78 allocs/op -BenchmarkR2router_ParseAll 72732 16584 ns/op 8352 B/op 120 allocs/op -BenchmarkRivet_ParseAll 281365 4968 ns/op 912 B/op 16 allocs/op -BenchmarkTango_ParseAll 42831 28668 ns/op 7168 B/op 208 allocs/op -BenchmarkTigerTonic_ParseAll 23774 49972 ns/op 16048 B/op 332 allocs/op -BenchmarkTraffic_ParseAll 10000 104679 ns/op 45520 B/op 605 allocs/op -BenchmarkVulcan_ParseAll 64810 18108 ns/op 2548 B/op 78 allocs/op -``` +Route: `/user/:name` with response write — Request: `GET /user/gordon` + +| Rank | Router | ns/op | B/op | allocs/op | +| :--: | :------------ | ----: | ----: | --------: | +| 1 | **BunRouter** | 25.86 | 0 | 0 | +| 2 | **Gin** | 27.65 | 0 | 0 | +| 3 | HttpRouter | 37.40 | 32 | 1 | +| 4 | Echo | 47.94 | 8 | 1 | +| 5 | Fiber | 125.7 | 0 | 0 | +| 6 | HttpTreeMux | 180.4 | 352 | 3 | +| 7 | Chi | 348.3 | 704 | 4 | +| 8 | Beego | 386.1 | 360 | 4 | +| 9 | Goji v2 | 516.9 | 1,168 | 10 | +| 10 | GorillaMux | 665.5 | 1,152 | 8 | +| 11 | Macaron | 784.3 | 1,112 | 13 | +| 12 | GoRestful | 1,534 | 4,608 | 16 | diff --git a/LICENSE b/LICENSE index 1ff7f370..49770f0c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Manuel Martínez-Almeida +Copyright (c) 2014-present Manuel Martínez-Almeida Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 1a7de86b..3c2da3b1 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) PACKAGES ?= $(shell $(GO) list ./...) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") -TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) +TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|ginS$$|binding$$|render$$' | grep -v examples) TESTTAGS ?= "" .PHONY: test @@ -12,7 +12,11 @@ TESTTAGS ?= "" test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + if [ -n "$(TESTTAGS)" ]; then \ + $(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + else \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + fi; \ cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ diff --git a/README.md b/README.md index 3c815afa..96b0ae3c 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) -## 📰 [Announcing Gin 1.12.0!](https://gin-gonic.com/en/blog/news/gin-1-12-0-release-announcement/) +## 📰 Gin 1.12.0 is now available! -Read about the latest features and improvements in Gin 1.11.0 on our official blog. +We're excited to announce the release of **[Gin 1.12.0](https://gin-gonic.com/en/blog/news/gin-1-12-0-release-announcement/)**! This release brings new features, performance improvements, and important bug fixes. Check out the [release announcement](https://gin-gonic.com/en/blog/news/gin-1-12-0-release-announcement/) on our official blog for the full details. --- @@ -209,7 +209,7 @@ Gin powers many high-traffic applications and services in production: ## 🤝 Contributing -Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions! +Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions! See the full list of [contributors](https://github.com/gin-gonic/gin/graphs/contributors). ### How to Contribute diff --git a/context.go b/context.go index fb68c3d3..2923f151 100644 --- a/context.go +++ b/context.go @@ -1229,6 +1229,12 @@ func (c *Context) XML(code int, obj any) { c.Render(code, render.XML{Data: obj}) } +// PDF writes the given PDF binary data into the response body. +// It also sets the Content-Type as "application/pdf". +func (c *Context) PDF(code int, data []byte) { + c.Render(code, render.PDF{Data: data}) +} + // YAML serializes the given struct as YAML into the response body. func (c *Context) YAML(code int, obj any) { c.Render(code, render.YAML{Data: obj}) diff --git a/context_test.go b/context_test.go index 44db7475..ef60379d 100644 --- a/context_test.go +++ b/context_test.go @@ -1320,6 +1320,33 @@ func TestContextRenderNoContentXML(t *testing.T) { assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } +// TestContextRenderPDF tests that the response is serialized as PDF +// and Content-Type is set to application/pdf +func TestContextRenderPDF(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + data := []byte("%Test pdf content") + c.PDF(http.StatusCreated, data) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, data, w.Body.Bytes()) + assert.Equal(t, "application/pdf", w.Header().Get("Content-Type")) +} + +// Tests that no PDF is rendered if code is 204 +func TestContextRenderNoContentPDF(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + data := []byte("%Test pdf content") + c.PDF(http.StatusNoContent, data) + + assert.Equal(t, http.StatusNoContent, w.Code) + assert.Empty(t, w.Body.Bytes()) + assert.Equal(t, "application/pdf", w.Header().Get("Content-Type")) +} + // TestContextRenderString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { diff --git a/docs/doc.md b/docs/doc.md index 7201df5c..d1c33b87 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -4,8 +4,8 @@ - [Build Tags](#build-tags) - [Build with json replacement](#build-with-json-replacement) - - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) -- [API Examples](#api-examples) + - [Build without MsgPack rendering feature](#build-without-msgpack-rendering-feature) +- [Routing](#routing) - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - [Parameters in path](#parameters-in-path) - [Querystring parameters](#querystring-parameters) @@ -16,24 +16,37 @@ - [Single file](#single-file) - [Multiple files](#multiple-files) - [Grouping routes](#grouping-routes) + - [Redirects](#redirects) +- [Middleware](#middleware) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) + - [Custom Middleware](#custom-middleware) - [Custom Recovery behavior](#custom-recovery-behavior) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) +- [Logging](#logging) - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) + - [Skip logging](#skip-logging) - [Controlling Log output coloring](#controlling-log-output-coloring) - - [Avoid logging query strings](#avoid-loging-query-strings) + - [Avoid logging query strings](#avoid-logging-query-strings) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) +- [Request Binding & Validation](#request-binding--validation) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind default value if none provided](#bind-default-value-if-none-provided) - - [Collection format for arrays](#collection-format-for-arrays) + - [Collection format for arrays](#collection-format-for-arrays) - [Bind Uri](#bind-uri) - [Bind custom unmarshaler](#bind-custom-unmarshaler) - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [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) + - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) +- [Response Rendering](#response-rendering) - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) - [SecureJSON](#securejson) - [JSONP](#jsonp) @@ -47,27 +60,22 @@ - [Custom Delimiters](#custom-delimiters) - [Custom Template Funcs](#custom-template-funcs) - [Multitemplate](#multitemplate) - - [Redirects](#redirects) - - [Custom Middleware](#custom-middleware) - - [Using BasicAuth() middleware](#using-basicauth-middleware) - - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Build a single binary with templates](#build-a-single-binary-with-templates) +- [Server Configuration](#server-configuration) - [Custom HTTP configuration](#custom-http-configuration) + - [Custom json codec at runtime](#custom-json-codec-at-runtime) - [Support Let's Encrypt](#support-lets-encrypt) - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful shutdown or restart](#graceful-shutdown-or-restart) - [Third-party packages](#third-party-packages) - [Manually](#manually) - - [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) - - [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) - - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) - - [Custom json codec at runtime](#custom-json-codec-at-runtime) -- [Don't trust all proxies](#dont-trust-all-proxies) + - [Don't trust all proxies](#dont-trust-all-proxies) - [Testing](#testing) +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). + ## Build tags ### Build with json replacement @@ -89,7 +97,7 @@ go build -tags=go_json . [sonic](https://github.com/bytedance/sonic) ```sh -$ go build -tags=sonic . +go build -tags=sonic . ``` ### Build without `MsgPack` rendering feature @@ -102,9 +110,9 @@ go build -tags=nomsgpack . This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). -## API Examples +## Routing -You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). +> Learn how to define routes, handle parameters, and organize endpoints. ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS @@ -361,6 +369,40 @@ func main() { } ``` +### Redirects + +Issuing a HTTP redirect is easy. Both internal and external locations are supported. + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") +}) +``` + +Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) + +```go +r.POST("/test", func(c *gin.Context) { + c.Redirect(http.StatusFound, "/foo") +}) +``` + +Issuing a Router redirect, use `HandleContext` like below. + +```go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"hello": "world"}) +}) +``` + +## Middleware + +> Configure middleware for request processing, authentication, and error recovery. + ### Blank Gin without middleware by default Use @@ -417,6 +459,46 @@ func main() { } ``` +### Custom Middleware + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Custom Recovery behavior ```go @@ -451,6 +533,82 @@ func main() { } ``` +### Using BasicAuth() middleware + +```go +// simulate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Goroutines inside a middleware + +When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +## Logging + +> Control log output, formatting, and filtering. + ### How to write log file ```go @@ -470,7 +628,7 @@ func main() { c.String(http.StatusOK, "pong") }) -   router.Run(":8080") + router.Run(":8080") } ``` @@ -598,15 +756,63 @@ func main() { ```go func main() { router := gin.New() - + // SkipQueryString indicates that the logger should not log the query string. // For example, /path?q=1 will be logged as /path loggerConfig := gin.LoggerConfig{SkipQueryString: true} - + router.Use(gin.LoggerWithConfig(loggerConfig)) } ``` +### Define format for the log of routes + +The default log of routes is: + +```sh +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +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. + +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + +## Request Binding & Validation + +> Bind and validate request data from query strings, forms, JSON, and more. + ### Model binding and validation 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). @@ -883,7 +1089,6 @@ Test it with: curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012" ``` - ### Bind default value if none provided If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag: @@ -892,44 +1097,44 @@ If the server should bind a default value to a field when the client does not pr package main import ( - "net/http" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) type Person struct { - Name string `form:"name,default=William"` - Age int `form:"age,default=10"` - Friends []string `form:"friends,default=Will;Bill"` - Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"` - LapTimes []int `form:"lap_times,default=1;2;3" collection_format:"csv"` + Name string `form:"name,default=William"` + Age int `form:"age,default=10"` + Friends []string `form:"friends,default=Will;Bill"` + Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"` + LapTimes []int `form:"lap_times,default=1;2;3" collection_format:"csv"` } func main() { - g := gin.Default() - g.POST("/person", func(c *gin.Context) { - var req Person - if err := c.ShouldBindQuery(&req); err != nil { - c.JSON(http.StatusBadRequest, err) - return - } - c.JSON(http.StatusOK, req) - }) - _ = g.Run("localhost:8080") + g := gin.Default() + g.POST("/person", func(c *gin.Context) { + var req Person + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, err) + return + } + c.JSON(http.StatusOK, req) + }) + _ = g.Run("localhost:8080") } ``` -``` +```sh curl -X POST http://localhost:8080/person {"Name":"William","Age":10,"Friends":["Will","Bill"],"Colors":["red","blue"],"LapTimes":[1,2,3]} ``` NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply: + - Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior - For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimit default values - Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv" - #### Collection format for arrays | Format | Description | Example | @@ -944,29 +1149,29 @@ NOTE: For default [collection values](#collection-format-for-arrays), the follow package main import ( - "log" - "time" - "github.com/gin-gonic/gin" + "log" + "time" + "github.com/gin-gonic/gin" ) type Person struct { - Name string `form:"name"` - Addresses []string `form:"addresses" collection_format:"csv"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + Name string `form:"name"` + Addresses []string `form:"addresses" collection_format:"csv"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") } func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Addresses) @@ -974,13 +1179,14 @@ func startPage(c *gin.Context) { log.Println(person.CreateTime) log.Println(person.UnixTime) } - c.String(200, "Success") + c.String(200, "Success") } ``` Test it with: + ```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +curl -X GET "localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` ### Bind Uri @@ -1065,12 +1271,15 @@ Test it with: ```sh curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02' ``` + Result + ```sh {"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]} ``` Note: + - If `parser=encoding.TextUnmarshaler` is specified for a type that does **not** implement `encoding.TextUnmarshaler`, gin will ignore it and proceed with its default binding logic. - If `parser=encoding.TextUnmarshaler` is specified for a type and that type's implementation of `encoding.TextUnmarshaler` returns an error, gin will stop binding and return the error to the client. @@ -1117,12 +1326,15 @@ Test it with: ```sh curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02' ``` + Result + ```sh {"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]} ``` Note: + - If a type implements both `encoding.TextUnmarshaler` and `BindUnmarshaler`, gin will use `BindUnmarshaler` by default unless you specify `parser=encoding.TextUnmarshaler` in the binding tag. - If a type returns an error from its implementation of `BindUnmarshaler`, gin will stop binding and return the error to the client. @@ -1194,21 +1406,21 @@ form.html ```html
-

Check some colors

- - - - - - - +

Check some colors

+ + + + + + +
``` result: ```json -{"color":["red","green","blue"]} +{ "color": ["red", "green", "blue"] } ``` ### Multipart/Urlencoded binding @@ -1255,6 +1467,202 @@ Test it with: curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +```sh +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // Calling c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith` or shortcuts. + +- `c.ShouldBindBodyWithJSON` is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +- `c.ShouldBindBodyWithXML` is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). +- `c.ShouldBindBodyWithYAML` is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). +- `c.ShouldBindBodyWithTOML` is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWithXML(&objB); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +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 + enough to call binding at once. +2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, + `ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, + can be called by `c.ShouldBind()` multiple times without any damage to + performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + +### Bind form-data request with custom struct and custom tag + +```go +const ( + customerTag = "url" + defaultMemory = 32 << 20 +) + +type customerBinding struct {} + +func (customerBinding) Name() string { + return "form" +} + +func (customerBinding) Bind(req *http.Request, obj any) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { + return err + } + return validate(obj) +} + +func validate(obj any) error { + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +} + +// Now we can do this!!! +// FormA is an external type that we can't modify it's tag +type FormA struct { + FieldA string `url:"field_a"` +} + +func ListHandler(s *Service) func(ctx *gin.Context) { + return func(ctx *gin.Context) { + var urlBinding = customerBinding{} + var opt FormA + err := ctx.MustBindWith(&opt, urlBinding) + if err != nil { + ... + } + ... + } +} +``` + +## Response Rendering + +> Render responses in various formats including JSON, XML, HTML, and more. + ### XML, JSON, YAML, TOML and ProtoBuf rendering ```go @@ -1499,9 +1907,7 @@ templates/index.tmpl ```html -

- {{ .title }} -

+

{{ .title }}

``` @@ -1529,10 +1935,9 @@ templates/posts/index.tmpl ```html {{ define "posts/index.tmpl" }} -

- {{ .title }} -

-

Using posts/index.tmpl

+ +

{{ .title }}

+

Using posts/index.tmpl

{{ end }} ``` @@ -1541,10 +1946,9 @@ templates/users/index.tmpl ```html {{ define "users/index.tmpl" }} -

- {{ .title }} -

-

Using users/index.tmpl

+ +

{{ .title }}

+

Using users/index.tmpl

{{ end }} ``` @@ -1630,147 +2034,62 @@ Date: 2017/07/01 Gin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. -### Redirects +### Build a single binary with templates -Issuing a HTTP redirect is easy. Both internal and external locations are supported. +You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. ```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` +package main -Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) +import ( + "embed" + "html/template" + "net/http" -```go -r.POST("/test", func(c *gin.Context) { - c.Redirect(http.StatusFound, "/foo") -}) -``` + "github.com/gin-gonic/gin" +) -Issuing a Router redirect, use `HandleContext` like below. - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"hello": "world"}) -}) -``` - -### Custom Middleware - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} +//go:embed assets/* templates/* +var f embed.FS func main() { - r := gin.New() - r.Use(Logger()) + router := gin.Default() + templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) + router.SetHTMLTemplate(templ) - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) + // example: /public/assets/images/example.png + router.StaticFS("/public", http.FS(f)) - // it would print: "12345" - log.Println(example) + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + router.GET("/foo", func(c *gin.Context) { + c.HTML(http.StatusOK, "bar.tmpl", gin.H{ + "title": "Foo website", + }) + }) + + router.GET("favicon.ico", func(c *gin.Context) { + file, _ := f.ReadFile("assets/favicon.ico") + c.Data( + http.StatusOK, + "image/x-icon", + file, + ) + }) + + router.Run(":8080") } ``` -### Using BasicAuth() middleware +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} +## Server Configuration -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Goroutines inside a middleware - -When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` +> Configure HTTP servers, TLS, proxies, and runtime settings. ### Custom HTTP configuration @@ -1800,6 +2119,65 @@ func main() { } ``` +### Custom json codec at runtime + +Gin support custom json serialization and deserialization logic without using compile tags. + +1. Define a custom struct implements the `json.Core` interface. + +2. Before your engine starts, assign values to `json.API` using the custom struct. + +```go +package main + +import ( + "io" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/codec/json" + jsoniter "github.com/json-iterator/go" +) + +var customConfig = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +// implement api.JsonApi +type customJsonApi struct { +} + +func (j customJsonApi) Marshal(v any) ([]byte, error) { + return customConfig.Marshal(v) +} + +func (j customJsonApi) Unmarshal(data []byte, v any) error { + return customConfig.Unmarshal(data, v) +} + +func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return customConfig.MarshalIndent(v, prefix, indent) +} + +func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { + return customConfig.NewEncoder(writer) +} + +func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { + return customConfig.NewDecoder(reader) +} + +func main() { + //Replace the default json api + json.API = customJsonApi{} + + //Start your gin engine + router := gin.Default() + router.Run(":8080") +} +``` + ### Support Let's Encrypt example for 1-line LetsEncrypt HTTPS servers. @@ -1965,9 +2343,9 @@ endless.ListenAndServe(":4242", router) Alternatives: -* [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. +- [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 @@ -2033,251 +2411,6 @@ func main() { } ``` -### Build a single binary with templates - -You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. - -```go -package main - -import ( - "embed" - "html/template" - "net/http" - - "github.com/gin-gonic/gin" -) - -//go:embed assets/* templates/* -var f embed.FS - -func main() { - router := gin.Default() - templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) - router.SetHTMLTemplate(templ) - - // example: /public/assets/images/example.png - router.StaticFS("/public", http.FS(f)) - - router.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - - router.GET("/foo", func(c *gin.Context) { - c.HTML(http.StatusOK, "bar.tmpl", gin.H{ - "title": "Foo website", - }) - }) - - router.GET("favicon.ico", func(c *gin.Context) { - file, _ := f.ReadFile("assets/favicon.ico") - c.Data( - http.StatusOK, - "image/x-icon", - file, - ) - }) - - router.Run(":8080") -} -``` - -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. - -### Bind form-data request with custom struct - -The follow example using custom struct: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -Using the command `curl` command result: - -```sh -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -### Try to bind body into different structs - -The normal methods for binding request body consumes `c.Request.Body` and they -cannot be called multiple times. - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // Calling c.ShouldBind consumes c.Request.Body and it cannot be reused. - if errA := c.ShouldBind(&objA); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // Always an error is occurred by this because c.Request.Body is EOF now. - } else if errB := c.ShouldBind(&objB); errB == nil { - c.String(http.StatusOK, `the body should be formB`) - } else { - ... - } -} -``` - -For this, you can use `c.ShouldBindBodyWith` or shortcuts. - -- `c.ShouldBindBodyWithJSON` is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). -- `c.ShouldBindBodyWithXML` is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). -- `c.ShouldBindBodyWithYAML` is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). -- `c.ShouldBindBodyWithTOML` is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). - -```go -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This reads c.Request.Body and stores the result into the context. - if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWithXML(&objB); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - -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 -enough to call binding at once. -2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, -`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, -can be called by `c.ShouldBind()` multiple times without any damage to -performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). - -### Bind form-data request with custom struct and custom tag - -```go -const ( - customerTag = "url" - defaultMemory = 32 << 20 -) - -type customerBinding struct {} - -func (customerBinding) Name() string { - return "form" -} - -func (customerBinding) Bind(req *http.Request, obj any) error { - if err := req.ParseForm(); err != nil { - return err - } - if err := req.ParseMultipartForm(defaultMemory); err != nil { - if err != http.ErrNotMultipart { - return err - } - } - if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { - return err - } - return validate(obj) -} - -func validate(obj any) error { - if binding.Validator == nil { - return nil - } - return binding.Validator.ValidateStruct(obj) -} - -// Now we can do this!!! -// FormA is an external type that we can't modify it's tag -type FormA struct { - FieldA string `url:"field_a"` -} - -func ListHandler(s *Service) func(ctx *gin.Context) { - return func(ctx *gin.Context) { - var urlBinding = customerBinding{} - var opt FormA - err := ctx.MustBindWith(&opt, urlBinding) - if err != nil { - ... - } - ... - } -} -``` - ### http2 server push http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. @@ -2327,50 +2460,6 @@ func main() { } ``` -### Define format for the log of routes - -The default log of routes is: - -```sh -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -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. - -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - ### Set and get a cookie ```go @@ -2451,66 +2540,7 @@ func main() { } ``` -### Custom json codec at runtime - -Gin support custom json serialization and deserialization logic without using compile tags. - -1. Define a custom struct implements the `json.Core` interface. - -2. Before your engine starts, assign values to `json.API` using the custom struct. - -```go -package main - -import ( - "io" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/codec/json" - jsoniter "github.com/json-iterator/go" -) - -var customConfig = jsoniter.Config{ - EscapeHTML: true, - SortMapKeys: true, - ValidateJsonRawMessage: true, -}.Froze() - -// implement api.JsonApi -type customJsonApi struct { -} - -func (j customJsonApi) Marshal(v any) ([]byte, error) { - return customConfig.Marshal(v) -} - -func (j customJsonApi) Unmarshal(data []byte, v any) error { - return customConfig.Unmarshal(data, v) -} - -func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { - return customConfig.MarshalIndent(v, prefix, indent) -} - -func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { - return customConfig.NewEncoder(writer) -} - -func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { - return customConfig.NewDecoder(reader) -} - -func main() { - //Replace the default json api - json.API = customJsonApi{} - - //Start your gin engine - router := gin.Default() - router.Run(":8080") -} -``` - -## Don't trust all proxies +### Don't trust all proxies Gin lets you specify which headers to hold the real client IP (if any), as well as specifying which proxies (or direct clients) you trust to diff --git a/gin_test.go b/gin_test.go index 43c9494d..a9cf1755 100644 --- a/gin_test.go +++ b/gin_test.go @@ -743,6 +743,78 @@ func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) { assert.Equal(t, int64(1), handlerCounterV2) } +func TestEngineHandleContextNoRouteWithGroupMiddleware(t *testing.T) { + // Scenario from issue #1848: + // - Engine with no global middleware (gin.New()) + // - A group with middleware + // - A route in that group + // - NoRoute handler that redirects via HandleContext + // The group middleware should run exactly once per HandleContext call, + // not accumulate across redirects. + + var middlewareCount, handlerCount int64 + + r := New() + grp := r.Group("", func(c *Context) { + atomic.AddInt64(&middlewareCount, 1) + c.Next() + }) + grp.GET("/target", func(c *Context) { + atomic.AddInt64(&handlerCount, 1) + c.String(http.StatusOK, "ok") + }) + + r.NoRoute(func(c *Context) { + c.Request.URL.Path = "/target" + r.HandleContext(c) + }) + + // Access a non-existent route to trigger NoRoute -> HandleContext + w := PerformRequest(r, "GET", "/nonexistent") + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "ok", w.Body.String()) + // Middleware and handler should each run exactly once + assert.Equal(t, int64(1), atomic.LoadInt64(&middlewareCount)) + assert.Equal(t, int64(1), atomic.LoadInt64(&handlerCount)) +} + +func TestEngineHandleContextNoRouteWithEngineMiddleware(t *testing.T) { + // When engine middleware exists and NoRoute redirects via HandleContext, + // verify the handlers run the expected number of times. + + var engineMwCount, groupMwCount, handlerCount int64 + + r := New() + r.Use(func(c *Context) { + atomic.AddInt64(&engineMwCount, 1) + c.Next() + }) + + grp := r.Group("", func(c *Context) { + atomic.AddInt64(&groupMwCount, 1) + c.Next() + }) + grp.GET("/target", func(c *Context) { + atomic.AddInt64(&handlerCount, 1) + c.String(http.StatusOK, "ok") + }) + + r.NoRoute(func(c *Context) { + c.Request.URL.Path = "/target" + r.HandleContext(c) + }) + + w := PerformRequest(r, "GET", "/nonexistent") + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "ok", w.Body.String()) + // Handler and group middleware should each run once (from HandleContext) + assert.Equal(t, int64(1), atomic.LoadInt64(&handlerCount)) + assert.Equal(t, int64(1), atomic.LoadInt64(&groupMwCount)) + // Engine middleware runs twice: once for the NoRoute chain, once for the HandleContext chain + // This is expected behavior since HandleContext re-enters the full handler chain + assert.Equal(t, int64(2), atomic.LoadInt64(&engineMwCount)) +} + func TestEngineHandleContextUseEscapedPathPercentEncoded(t *testing.T) { r := New() r.UseEscapedPath = true diff --git a/go.mod b/go.mod index 4fe2ce4f..c5481db5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bytedance/sonic v1.15.0 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.30.1 - github.com/goccy/go-json v0.10.5 + github.com/goccy/go-json v0.10.6 github.com/goccy/go-yaml v1.19.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 @@ -16,8 +16,8 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 go.mongodb.org/mongo-driver/v2 v2.5.0 - golang.org/x/net v0.51.0 - google.golang.org/protobuf v1.36.10 + golang.org/x/net v0.52.0 + google.golang.org/protobuf v1.36.11 ) require gopkg.in/yaml.v3 v3.0.1 // indirect @@ -27,7 +27,7 @@ require ( github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -38,8 +38,8 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.6.0 // indirect - golang.org/x/arch v0.22.0 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/arch v0.25.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect ) diff --git a/go.sum b/go.sum index a75260ce..a3b5b8a3 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -22,8 +22,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -75,19 +75,19 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= -golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= +golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/render/pdf.go b/render/pdf.go new file mode 100644 index 00000000..04dcc1f5 --- /dev/null +++ b/render/pdf.go @@ -0,0 +1,26 @@ +// Copyright 2026 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" + +// PDF contains the given PDF binary data. +type PDF struct { + Data []byte +} + +var pdfContentType = []string{"application/pdf"} + +// Render (PDF) writes PDF data with custom ContentType. +func (r PDF) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + _, err := w.Write(r.Data) + return err +} + +// WriteContentType (PDF) writes PDF ContentType for response. +func (r PDF) WriteContentType(w http.ResponseWriter) { + writeContentType(w, pdfContentType) +} diff --git a/render/render.go b/render/render.go index 4bdcfa23..28bc0f5d 100644 --- a/render/render.go +++ b/render/render.go @@ -31,6 +31,7 @@ var ( _ Render = (*AsciiJSON)(nil) _ Render = (*ProtoBuf)(nil) _ Render = (*TOML)(nil) + _ Render = (*PDF)(nil) ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index b48ab3d3..f63878b9 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -466,6 +466,22 @@ func TestRenderXMLError(t *testing.T) { assert.Contains(t, err.Error(), "xml: unsupported type: chan int") } +func TestRenderPDF(t *testing.T) { + w := httptest.NewRecorder() + data := []byte("%Test pdf content") + + pdf := PDF{data} + + pdf.WriteContentType(w) + assert.Equal(t, "application/pdf", w.Header().Get("Content-Type")) + + err := pdf.Render(w) + require.NoError(t, err) + + assert.Equal(t, data, w.Body.Bytes()) + assert.Equal(t, "application/pdf", w.Header().Get("Content-Type")) +} + func TestRenderRedirect(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil) require.NoError(t, err) diff --git a/test_helpers.go b/test_helpers.go index 20d20032..146be8f3 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -49,10 +49,7 @@ func waitForServerReady(url string, maxAttempts int) error { } // Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms... - backoff := time.Duration(10*(1< 500*time.Millisecond { - backoff = 500 * time.Millisecond - } + backoff := min(time.Duration(10*(1<