pax_global_header00006660000000000000000000000064136535471640014530gustar00rootroot0000000000000052 comment=6a8b8b6651a228590bbc42145483ff864f4f6e89 gin-1.6.3/000077500000000000000000000000001365354716400123145ustar00rootroot00000000000000gin-1.6.3/.github/000077500000000000000000000000001365354716400136545ustar00rootroot00000000000000gin-1.6.3/.github/ISSUE_TEMPLATE.md000066400000000000000000000016261365354716400163660ustar00rootroot00000000000000- With issues: - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. ## Description ## How to reproduce ``` package main import ( "github.com/gin-gonic/gin" ) func main() { g := gin.Default() g.GET("/hello/:name", func(c *gin.Context) { c.String(200, "Hello %s", c.Param("name")) }) g.Run(":9000") } ``` ## Expectations ``` $ curl http://localhost:8201/hello/world Hello world ``` ## Actual result ``` $ curl -i http://localhost:8201/hello/world ``` ## Environment - go version: - gin version (or commit ref): - operating system: gin-1.6.3/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000006261365354716400174610ustar00rootroot00000000000000- With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. gin-1.6.3/.gitignore000066400000000000000000000001151365354716400143010ustar00rootroot00000000000000vendor/* !vendor/vendor.json coverage.out count.out test profile.out tmp.out gin-1.6.3/.travis.yml000066400000000000000000000021141365354716400144230ustar00rootroot00000000000000language: go matrix: fast_finish: true include: - go: 1.11.x env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on - go: 1.13.x - go: 1.13.x env: - TESTTAGS=nomsgpack - go: 1.14.x - go: 1.14.x env: - TESTTAGS=nomsgpack - go: master git: depth: 10 before_install: - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi install: - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi go_import_path: github.com/gin-gonic/gin script: - make vet - make fmt-check - make misspell-check - make test after_success: - bash <(curl -s https://codecov.io/bash) notifications: webhooks: urls: - https://webhooks.gitter.im/e/7f95bf605c4d356372f4 on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: false # default: false gin-1.6.3/AUTHORS.md000066400000000000000000000101631365354716400137640ustar00rootroot00000000000000List of all the awesome people working to make Gin the best Web Framework in Go. ## gin 1.x series authors **Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) ## gin 0.x series authors **Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) People and companies, who have contributed, in alphabetical order. **@858806258 (杰哥)** - Fix typo in example **@achedeuzot (Klemen Sever)** - Fix newline debug printing **@adammck (Adam Mckaig)** - Add MIT license **@AlexanderChen1989 (Alexander)** - Typos in README **@alexanderdidenko (Aleksandr Didenko)** - Add support multipart/form-data **@alexandernyquist (Alexander Nyquist)** - Using template.Must to fix multiple return issue - ★ Added support for OPTIONS verb - ★ Setting response headers before calling WriteHeader - Improved documentation for model binding - ★ Added Content.Redirect() - ★ Added tons of Unit tests **@austinheap (Austin Heap)** - Added travis CI integration **@andredublin (Andre Dublin)** - Fix typo in comment **@bredov (Ludwig Valda Vasquez)** - Fix html templating in debug mode **@bluele (Jun Kimura)** - Fixes code examples in README **@chad-russell** - ★ Support for serializing gin.H into XML **@dickeyxxx (Jeff Dickey)** - Typos in README - Add example about serving static files **@donileo (Adonis)** - Add NoMethod handler **@dutchcoders (DutchCoders)** - ★ Fix security bug that allows client to spoof ip - Fix typo. r.HTMLTemplates -> SetHTMLTemplate **@el3ctro- (Joshua Loper)** - Fix typo in example **@ethankan (Ethan Kan)** - Unsigned integers in binding **(Evgeny Persienko)** - Validate sub structures **@frankbille (Frank Bille)** - Add support for HTTP Realm Auth **@fmd (Fareed Dudhia)** - Fix typo. SetHTTPTemplate -> SetHTMLTemplate **@ironiridis (Christopher Harrington)** - Remove old reference **@jammie-stackhouse (Jamie Stackhouse)** - Add more shortcuts for router methods **@jasonrhansen** - Fix spelling and grammar errors in documentation **@JasonSoft (Jason Lee)** - Fix typo in comment **@joiggama (Ignacio Galindo)** - Add utf-8 charset header on renders **@julienschmidt (Julien Schmidt)** - gofmt the code examples **@kelcecil (Kel Cecil)** - Fix readme typo **@kyledinh (Kyle Dinh)** - Adds RunTLS() **@LinusU (Linus Unnebäck)** - Small fixes in README **@loongmxbt (Saint Asky)** - Fix typo in example **@lucas-clemente (Lucas Clemente)** - ★ work around path.Join removing trailing slashes from routes **@mattn (Yasuhiro Matsumoto)** - Improve color logger **@mdigger (Dmitry Sedykh)** - Fixes Form binding when content-type is x-www-form-urlencoded - No repeat call c.Writer.Status() in gin.Logger - Fixes Content-Type for json render **@mirzac (Mirza Ceric)** - Fix debug printing **@mopemope (Yutaka Matsubara)** - ★ Adds Godep support (Dependencies Manager) - Fix variadic parameter in the flexible render API - Fix Corrupted plain render - Add Pluggable View Renderer Example **@msemenistyi (Mykyta Semenistyi)** - update Readme.md. Add code to String method **@msoedov (Sasha Myasoedov)** - ★ Adds tons of unit tests. **@ngerakines (Nick Gerakines)** - ★ Improves API, c.GET() doesn't panic - Adds MustGet() method **@r8k (Rajiv Kilaparti)** - Fix Port usage in README. **@rayrod2030 (Ray Rodriguez)** - Fix typo in example **@rns** - Fix typo in example **@RobAWilkinson (Robert Wilkinson)** - Add example of forms and params **@rogierlommers (Rogier Lommers)** - Add updated static serve example **@se77en (Damon Zhao)** - Improve color logging **@silasb (Silas Baronda)** - Fixing quotes in README **@SkuliOskarsson (Skuli Oskarsson)** - Fixes some texts in README II **@slimmy (Jimmy Pettersson)** - Added messages for required bindings **@smira (Andrey Smirnov)** - Add support for ignored/unexported fields in binding **@superalsrk (SRK.Lyu)** - Update httprouter godeps **@tebeka (Miki Tebeka)** - Use net/http constants instead of numeric values **@techjanitor** - Update context.go reserved IPs **@yosssi (Keiji Yoshida)** - Fix link in README **@yuyabee** - Fixed README gin-1.6.3/BENCHMARKS.md000066400000000000000000001673731365354716400142740ustar00rootroot00000000000000 ## Benchmark System **VM HOST:** DigitalOcean **Machine:** 12 CPU, 24 GB RAM. Ubuntu 16.04.2 x64 **Date:** Nov 26th, 2019 **Go Version:** 1.13.4 linux/amd64 **Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark) **Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) ## Static Routes: 157 ``` Gin: 34936 Bytes HttpServeMux: 14512 Bytes Ace: 30648 Bytes Aero: 800696 Bytes Bear: 30664 Bytes Beego: 98456 Bytes Bone: 40224 Bytes Chi: 83608 Bytes CloudyKitRouter: 30448 Bytes Denco: 9928 Bytes Echo: 76584 Bytes GocraftWeb: 55496 Bytes Goji: 29744 Bytes Gojiv2: 105840 Bytes GoJsonRest: 137512 Bytes GoRestful: 816936 Bytes GorillaMux: 585632 Bytes GowwwRouter: 24968 Bytes HttpRouter: 21680 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 ``` ## GithubAPI Routes: 203 ``` Gin: 58512 Bytes Ace: 48640 Bytes Aero: 1386208 Bytes Bear: 82536 Bytes Beego: 150936 Bytes Bone: 100976 Bytes Chi: 95112 Bytes CloudyKitRouter: 93704 Bytes Denco: 36736 Bytes Echo: 96328 Bytes GocraftWeb: 95432 Bytes Goji: 51600 Bytes Gojiv2: 104704 Bytes GoJsonRest: 142024 Bytes GoRestful: 1241656 Bytes GorillaMux: 1322784 Bytes GowwwRouter: 80008 Bytes HttpRouter: 37096 Bytes HttpTreeMux: 78800 Bytes Kocha: 785408 Bytes LARS: 48600 Bytes Macaron: 93680 Bytes Martini: 485264 Bytes Pat: 21200 Bytes Possum: 85312 Bytes R2router: 47104 Bytes Rivet: 42840 Bytes Tango: 54840 Bytes TigerTonic: 96176 Bytes Traffic: 921744 Bytes Vulcan: 425368 Bytes ``` ## GPlusAPI Routes: 13 ``` Gin: 4384 Bytes Ace: 3 664 Bytes Aero: 88248 Bytes Bear: 7112 Bytes Beego: 10272 Bytes Bone: 6688 Bytes Chi: 8024 Bytes CloudyKitRouter: 6728 Bytes Denco: 3264 Bytes Echo: 9272 Bytes GocraftWeb: 7496 Bytes Goji: 3152 Bytes Gojiv2: 7376 Bytes GoJsonRest: 11416 Bytes GoRestful: 74328 Bytes GorillaMux: 66208 Bytes GowwwRouter: 5744 Bytes HttpRouter: 2760 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 ``` ## ParseAPI Routes: 26 ``` Gin: 7776 Bytes Ace: 6656 Bytes Aero: 163736 Bytes Bear: 12528 Bytes Beego: 19280 Bytes Bone: 11440 Bytes Chi: 9744 Bytes Denco: 4192 Bytes Echo: 11648 Bytes GocraftWeb: 12800 Bytes Goji: 5680 Bytes Gojiv2: 14464 Bytes GoJsonRest: 14424 Bytes GoRestful: 116264 Bytes GorillaMux: 105880 Bytes GowwwRouter: 9344 Bytes HttpRouter: 5024 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 ``` ## Static Routes ``` BenchmarkGin_StaticAll 25604 45487 ns/op 0 B/op 0 allocs/op BenchmarkAce_StaticAll 28402 42046 ns/op 0 B/op 0 allocs/op BenchmarkAero_StaticAll 38766 30333 ns/op 0 B/op 0 allocs/op BenchmarkHttpServeMux_StaticAll 25728 46511 ns/op 0 B/op 0 allocs/op BenchmarkBeego_StaticAll 5098 288527 ns/op 55264 B/op 471 allocs/op BenchmarkBear_StaticAll 10000 126323 ns/op 20272 B/op 469 allocs/op BenchmarkBone_StaticAll 9499 113631 ns/op 0 B/op 0 allocs/op BenchmarkChi_StaticAll 7912 237363 ns/op 67824 B/op 471 allocs/op BenchmarkCloudyKitRouter_StaticAll 41626 28668 ns/op 0 B/op 0 allocs/op BenchmarkDenco_StaticAll 95774 12221 ns/op 0 B/op 0 allocs/op BenchmarkEcho_StaticAll 26246 44603 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_StaticAll 10000 193337 ns/op 46312 B/op 785 allocs/op BenchmarkGoji_StaticAll 15886 75789 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_StaticAll 1886 597374 ns/op 205984 B/op 1570 allocs/op BenchmarkGoJsonRest_StaticAll 4700 307144 ns/op 51653 B/op 1727 allocs/op BenchmarkGoRestful_StaticAll 429 2880165 ns/op 613280 B/op 2053 allocs/op BenchmarkGorillaMux_StaticAll 754 1491761 ns/op 153233 B/op 1413 allocs/op BenchmarkGowwwRouter_StaticAll 28071 42629 ns/op 0 B/op 0 allocs/op BenchmarkHttpRouter_StaticAll 47672 24875 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_StaticAll 46770 25100 ns/op 0 B/op 0 allocs/op BenchmarkKocha_StaticAll 61045 19494 ns/op 0 B/op 0 allocs/op BenchmarkLARS_StaticAll 36103 32700 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_StaticAll 4261 430131 ns/op 115552 B/op 1256 allocs/op BenchmarkMartini_StaticAll 481 2320157 ns/op 125444 B/op 1717 allocs/op BenchmarkPat_StaticAll 325 3739521 ns/op 602832 B/op 12559 allocs/op BenchmarkPossum_StaticAll 10000 203575 ns/op 65312 B/op 471 allocs/op BenchmarkR2router_StaticAll 10000 110536 ns/op 22608 B/op 628 allocs/op BenchmarkRivet_StaticAll 23344 51174 ns/op 0 B/op 0 allocs/op BenchmarkTango_StaticAll 3596 340045 ns/op 39209 B/op 1256 allocs/op BenchmarkTigerTonic_StaticAll 16784 71807 ns/op 7376 B/op 157 allocs/op BenchmarkTraffic_StaticAll 350 3435155 ns/op 754862 B/op 14601 allocs/op BenchmarkVulcan_StaticAll 5930 200284 ns/op 15386 B/op 471 allocs/op ``` ## Micro Benchmarks ``` BenchmarkGin_Param 8623915 139 ns/op 0 B/op 0 allocs/op BenchmarkAce_Param 3976539 290 ns/op 32 B/op 1 allocs/op BenchmarkAero_Param 8948976 133 ns/op 0 B/op 0 allocs/op BenchmarkBear_Param 1000000 1277 ns/op 456 B/op 5 allocs/op BenchmarkBeego_Param 889404 1785 ns/op 352 B/op 3 allocs/op BenchmarkBone_Param 1000000 2219 ns/op 816 B/op 6 allocs/op BenchmarkChi_Param 1000000 1386 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_Param 18343244 61.2 ns/op 0 B/op 0 allocs/op BenchmarkDenco_Param 5637424 204 ns/op 32 B/op 1 allocs/op BenchmarkEcho_Param 9540910 122 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param 1000000 1939 ns/op 648 B/op 8 allocs/op BenchmarkGoji_Param 1283509 938 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Param 331266 3554 ns/op 1328 B/op 11 allocs/op BenchmarkGoJsonRest_Param 908851 2158 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_Param 135781 9339 ns/op 4192 B/op 14 allocs/op BenchmarkGorillaMux_Param 308407 3893 ns/op 1280 B/op 10 allocs/op BenchmarkGowwwRouter_Param 1000000 1044 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_Param 6653476 162 ns/op 32 B/op 1 allocs/op BenchmarkHttpTreeMux_Param 1361378 819 ns/op 352 B/op 3 allocs/op BenchmarkKocha_Param 3084330 353 ns/op 56 B/op 3 allocs/op BenchmarkLARS_Param 11502079 107 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param 439095 3750 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_Param 177099 7479 ns/op 1072 B/op 10 allocs/op BenchmarkPat_Param 729747 2048 ns/op 536 B/op 11 allocs/op BenchmarkPossum_Param 995989 1705 ns/op 496 B/op 5 allocs/op BenchmarkR2router_Param 1000000 1037 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Param 4057065 271 ns/op 48 B/op 1 allocs/op BenchmarkTango_Param 812029 1682 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_Param 450592 3358 ns/op 776 B/op 16 allocs/op BenchmarkTraffic_Param 206390 5661 ns/op 1856 B/op 21 allocs/op BenchmarkVulcan_Param 1441147 792 ns/op 98 B/op 3 allocs/op BenchmarkAce_Param5 1891473 632 ns/op 160 B/op 1 allocs/op BenchmarkAero_Param5 5191258 227 ns/op 0 B/op 0 allocs/op BenchmarkBear_Param5 988882 1734 ns/op 501 B/op 5 allocs/op BenchmarkBeego_Param5 625438 2132 ns/op 352 B/op 3 allocs/op BenchmarkBone_Param5 622030 3061 ns/op 864 B/op 6 allocs/op BenchmarkChi_Param5 1000000 1735 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_Param5 5167868 225 ns/op 0 B/op 0 allocs/op BenchmarkDenco_Param5 2174550 550 ns/op 160 B/op 1 allocs/op BenchmarkEcho_Param5 4272258 275 ns/op 0 B/op 0 allocs/op BenchmarkGin_Param5 4190391 275 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param5 623739 3107 ns/op 920 B/op 11 allocs/op BenchmarkGoji_Param5 1000000 1310 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Param5 314694 3803 ns/op 1392 B/op 11 allocs/op BenchmarkGoJsonRest_Param5 308203 4108 ns/op 1097 B/op 16 allocs/op BenchmarkGoRestful_Param5 115048 9787 ns/op 4288 B/op 14 allocs/op BenchmarkGorillaMux_Param5 180812 5658 ns/op 1344 B/op 10 allocs/op BenchmarkGowwwRouter_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_Param5 2395767 502 ns/op 160 B/op 1 allocs/op BenchmarkHttpTreeMux_Param5 899263 2096 ns/op 576 B/op 6 allocs/op BenchmarkKocha_Param5 1000000 1639 ns/op 440 B/op 10 allocs/op BenchmarkLARS_Param5 5807994 203 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param5 272967 4087 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_Param5 120735 8886 ns/op 1232 B/op 11 allocs/op BenchmarkPat_Param5 294714 4943 ns/op 888 B/op 29 allocs/op BenchmarkPossum_Param5 1000000 1689 ns/op 496 B/op 5 allocs/op BenchmarkR2router_Param5 1000000 1319 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Param5 1347289 883 ns/op 240 B/op 1 allocs/op BenchmarkTango_Param5 617077 2091 ns/op 360 B/op 8 allocs/op BenchmarkTigerTonic_Param5 113659 11212 ns/op 2279 B/op 39 allocs/op BenchmarkTraffic_Param5 134148 9039 ns/op 2208 B/op 27 allocs/op BenchmarkVulcan_Param5 1000000 1095 ns/op 98 B/op 3 allocs/op BenchmarkAce_Param20 1000000 1838 ns/op 640 B/op 1 allocs/op BenchmarkAero_Param20 17120668 66.1 ns/op 0 B/op 0 allocs/op BenchmarkBear_Param20 205585 5332 ns/op 1665 B/op 5 allocs/op BenchmarkBeego_Param20 230522 5382 ns/op 352 B/op 3 allocs/op BenchmarkBone_Param20 167190 8076 ns/op 2031 B/op 6 allocs/op BenchmarkChi_Param20 480528 3044 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_Param20 1347794 872 ns/op 0 B/op 0 allocs/op BenchmarkDenco_Param20 1000000 1867 ns/op 640 B/op 1 allocs/op BenchmarkEcho_Param20 1363526 897 ns/op 0 B/op 0 allocs/op BenchmarkGin_Param20 1607217 748 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param20 97314 11671 ns/op 3795 B/op 15 allocs/op BenchmarkGoji_Param20 289407 4220 ns/op 1246 B/op 2 allocs/op BenchmarkGojiv2_Param20 245186 4869 ns/op 1632 B/op 11 allocs/op BenchmarkGoJsonRest_Param20 78049 15725 ns/op 4485 B/op 20 allocs/op BenchmarkGoRestful_Param20 66907 18031 ns/op 6716 B/op 18 allocs/op BenchmarkGorillaMux_Param20 81866 12422 ns/op 3452 B/op 12 allocs/op BenchmarkGowwwRouter_Param20 955983 1688 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_Param20 1000000 1629 ns/op 640 B/op 1 allocs/op BenchmarkHttpTreeMux_Param20 108940 10241 ns/op 3195 B/op 10 allocs/op BenchmarkKocha_Param20 197022 5488 ns/op 1808 B/op 27 allocs/op BenchmarkLARS_Param20 2451241 490 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param20 106770 10788 ns/op 2923 B/op 12 allocs/op BenchmarkMartini_Param20 69028 17112 ns/op 3596 B/op 13 allocs/op BenchmarkPat_Param20 56275 21535 ns/op 4424 B/op 93 allocs/op BenchmarkPossum_Param20 1000000 1705 ns/op 496 B/op 5 allocs/op BenchmarkR2router_Param20 172215 7099 ns/op 2283 B/op 7 allocs/op BenchmarkRivet_Param20 447265 2987 ns/op 1024 B/op 1 allocs/op BenchmarkTango_Param20 327494 3850 ns/op 856 B/op 8 allocs/op BenchmarkTigerTonic_Param20 27176 44571 ns/op 9871 B/op 119 allocs/op BenchmarkTraffic_Param20 38828 31025 ns/op 7856 B/op 47 allocs/op BenchmarkVulcan_Param20 560442 1807 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParamWrite 2712150 442 ns/op 40 B/op 2 allocs/op BenchmarkAero_ParamWrite 6392880 189 ns/op 0 B/op 0 allocs/op BenchmarkBear_ParamWrite 1000000 1338 ns/op 456 B/op 5 allocs/op BenchmarkBeego_ParamWrite 821431 1886 ns/op 360 B/op 4 allocs/op BenchmarkBone_ParamWrite 913227 2350 ns/op 816 B/op 6 allocs/op BenchmarkChi_ParamWrite 1000000 1427 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_ParamWrite 18645724 60.9 ns/op 0 B/op 0 allocs/op BenchmarkDenco_ParamWrite 4394764 264 ns/op 32 B/op 1 allocs/op BenchmarkEcho_ParamWrite 5288883 242 ns/op 8 B/op 1 allocs/op BenchmarkGin_ParamWrite 4584932 253 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParamWrite 866242 2094 ns/op 656 B/op 9 allocs/op BenchmarkGoji_ParamWrite 1201875 1004 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_ParamWrite 317766 3777 ns/op 1360 B/op 13 allocs/op BenchmarkGoJsonRest_ParamWrite 380242 3447 ns/op 1128 B/op 18 allocs/op BenchmarkGoRestful_ParamWrite 131046 9340 ns/op 4200 B/op 15 allocs/op BenchmarkGorillaMux_ParamWrite 298428 3970 ns/op 1280 B/op 10 allocs/op BenchmarkGowwwRouter_ParamWrite 655940 2744 ns/op 976 B/op 8 allocs/op BenchmarkHttpRouter_ParamWrite 5237014 219 ns/op 32 B/op 1 allocs/op BenchmarkHttpTreeMux_ParamWrite 1379904 853 ns/op 352 B/op 3 allocs/op BenchmarkKocha_ParamWrite 2939042 400 ns/op 56 B/op 3 allocs/op BenchmarkLARS_ParamWrite 6181642 199 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParamWrite 352497 4670 ns/op 1176 B/op 14 allocs/op BenchmarkMartini_ParamWrite 138259 8543 ns/op 1176 B/op 14 allocs/op BenchmarkPat_ParamWrite 552386 3262 ns/op 960 B/op 15 allocs/op BenchmarkPossum_ParamWrite 1000000 1711 ns/op 496 B/op 5 allocs/op BenchmarkR2router_ParamWrite 1000000 1085 ns/op 432 B/op 5 allocs/op BenchmarkRivet_ParamWrite 2374513 489 ns/op 112 B/op 2 allocs/op BenchmarkTango_ParamWrite 1443907 812 ns/op 136 B/op 4 allocs/op BenchmarkTigerTonic_ParamWrite 324264 4874 ns/op 1216 B/op 21 allocs/op BenchmarkTraffic_ParamWrite 170726 7155 ns/op 2280 B/op 25 allocs/op BenchmarkVulcan_ParamWrite 1498888 776 ns/op 98 B/op 3 allocs/op ``` ## GitHub ``` BenchmarkGin_GithubStatic 5866748 194 ns/op 0 B/op 0 allocs/op BenchmarkAce_GithubStatic 5815826 205 ns/op 0 B/op 0 allocs/op BenchmarkAero_GithubStatic 10822906 106 ns/op 0 B/op 0 allocs/op BenchmarkBear_GithubStatic 1678065 707 ns/op 120 B/op 3 allocs/op BenchmarkBeego_GithubStatic 828814 1717 ns/op 352 B/op 3 allocs/op BenchmarkBone_GithubStatic 67484 18858 ns/op 2880 B/op 60 allocs/op BenchmarkCloudyKitRouter_GithubStatic 10219297 115 ns/op 0 B/op 0 allocs/op BenchmarkChi_GithubStatic 1000000 1348 ns/op 432 B/op 3 allocs/op BenchmarkDenco_GithubStatic 15220622 75.4 ns/op 0 B/op 0 allocs/op BenchmarkEcho_GithubStatic 7255897 158 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GithubStatic 1000000 1198 ns/op 296 B/op 5 allocs/op BenchmarkGoji_GithubStatic 3659361 320 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_GithubStatic 402402 3384 ns/op 1312 B/op 10 allocs/op BenchmarkGoRestful_GithubStatic 54592 22045 ns/op 4256 B/op 13 allocs/op BenchmarkGoJsonRest_GithubStatic 801067 1673 ns/op 329 B/op 11 allocs/op BenchmarkGorillaMux_GithubStatic 169690 8171 ns/op 976 B/op 9 allocs/op BenchmarkGowwwRouter_GithubStatic 5372910 218 ns/op 0 B/op 0 allocs/op BenchmarkHttpRouter_GithubStatic 10965576 103 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_GithubStatic 10505365 106 ns/op 0 B/op 0 allocs/op BenchmarkKocha_GithubStatic 14153763 81.9 ns/op 0 B/op 0 allocs/op BenchmarkLARS_GithubStatic 7874017 152 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubStatic 696940 2678 ns/op 736 B/op 8 allocs/op BenchmarkMartini_GithubStatic 102384 12276 ns/op 768 B/op 9 allocs/op BenchmarkPat_GithubStatic 69907 17437 ns/op 3648 B/op 76 allocs/op BenchmarkPossum_GithubStatic 1000000 1262 ns/op 416 B/op 3 allocs/op BenchmarkR2router_GithubStatic 1981592 614 ns/op 144 B/op 4 allocs/op BenchmarkRivet_GithubStatic 6103872 196 ns/op 0 B/op 0 allocs/op BenchmarkTango_GithubStatic 629551 2023 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_GithubStatic 2801569 424 ns/op 48 B/op 1 allocs/op BenchmarkTraffic_GithubStatic 63716 18009 ns/op 4664 B/op 90 allocs/op BenchmarkVulcan_GithubStatic 885640 1177 ns/op 98 B/op 3 allocs/op BenchmarkAce_GithubParam 2016942 582 ns/op 96 B/op 1 allocs/op BenchmarkAero_GithubParam 4009522 296 ns/op 0 B/op 0 allocs/op BenchmarkBear_GithubParam 1000000 1575 ns/op 496 B/op 5 allocs/op BenchmarkBeego_GithubParam 796662 2038 ns/op 352 B/op 3 allocs/op BenchmarkBone_GithubParam 114823 10325 ns/op 1888 B/op 19 allocs/op BenchmarkChi_GithubParam 1000000 1783 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_GithubParam 3910996 303 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GithubParam 2298172 521 ns/op 128 B/op 1 allocs/op BenchmarkEcho_GithubParam 3336364 357 ns/op 0 B/op 0 allocs/op BenchmarkGin_GithubParam 2729161 439 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GithubParam 825784 2338 ns/op 712 B/op 9 allocs/op BenchmarkGoji_GithubParam 933397 1559 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GithubParam 253884 4335 ns/op 1408 B/op 13 allocs/op BenchmarkGoJsonRest_GithubParam 575532 2967 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_GithubParam 38160 30638 ns/op 4352 B/op 16 allocs/op BenchmarkGorillaMux_GithubParam 94554 12035 ns/op 1296 B/op 10 allocs/op BenchmarkGowwwRouter_GithubParam 1000000 1223 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_GithubParam 2562079 468 ns/op 96 B/op 1 allocs/op BenchmarkHttpTreeMux_GithubParam 1000000 1386 ns/op 384 B/op 4 allocs/op BenchmarkKocha_GithubParam 1573026 754 ns/op 128 B/op 5 allocs/op BenchmarkLARS_GithubParam 4203394 282 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubParam 365078 4137 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_GithubParam 71608 15811 ns/op 1152 B/op 11 allocs/op BenchmarkPat_GithubParam 92768 13297 ns/op 2408 B/op 48 allocs/op BenchmarkPossum_GithubParam 1000000 1704 ns/op 496 B/op 5 allocs/op BenchmarkR2router_GithubParam 1000000 1120 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GithubParam 1642794 720 ns/op 96 B/op 1 allocs/op BenchmarkTango_GithubParam 574195 2345 ns/op 344 B/op 8 allocs/op BenchmarkTigerTonic_GithubParam 272430 5493 ns/op 1176 B/op 22 allocs/op BenchmarkTraffic_GithubParam 81914 15078 ns/op 2816 B/op 40 allocs/op BenchmarkVulcan_GithubParam 581272 1902 ns/op 98 B/op 3 allocs/op BenchmarkAce_GithubAll 10000 103571 ns/op 13792 B/op 167 allocs/op BenchmarkAero_GithubAll 21366 55615 ns/op 0 B/op 0 allocs/op BenchmarkBear_GithubAll 5288 327648 ns/op 86448 B/op 943 allocs/op BenchmarkBeego_GithubAll 3974 413453 ns/op 71456 B/op 609 allocs/op BenchmarkBone_GithubAll 267 4450294 ns/op 720160 B/op 8620 allocs/op BenchmarkChi_GithubAll 5067 358773 ns/op 87696 B/op 609 allocs/op BenchmarkCloudyKitRouter_GithubAll 24210 49233 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GithubAll 12508 95341 ns/op 20224 B/op 167 allocs/op BenchmarkEcho_GithubAll 16353 73267 ns/op 0 B/op 0 allocs/op BenchmarkGin_GithubAll 15516 77716 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GithubAll 2908 466970 ns/op 131656 B/op 1686 allocs/op BenchmarkGoji_GithubAll 1746 691392 ns/op 56112 B/op 334 allocs/op BenchmarkGojiv2_GithubAll 954 1289604 ns/op 352720 B/op 4321 allocs/op BenchmarkGoJsonRest_GithubAll 2013 599088 ns/op 134371 B/op 2737 allocs/op BenchmarkGoRestful_GithubAll 223 5404307 ns/op 910144 B/op 2938 allocs/op BenchmarkGorillaMux_GithubAll 202 5943565 ns/op 251650 B/op 1994 allocs/op BenchmarkGowwwRouter_GithubAll 9009 227799 ns/op 72144 B/op 501 allocs/op BenchmarkHttpRouter_GithubAll 14570 78718 ns/op 13792 B/op 167 allocs/op BenchmarkHttpTreeMux_GithubAll 7226 242491 ns/op 65856 B/op 671 allocs/op BenchmarkKocha_GithubAll 8282 159873 ns/op 23304 B/op 843 allocs/op BenchmarkLARS_GithubAll 22711 52745 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubAll 2067 563117 ns/op 149409 B/op 1624 allocs/op BenchmarkMartini_GithubAll 218 5455290 ns/op 226552 B/op 2325 allocs/op BenchmarkPat_GithubAll 174 6801582 ns/op 1483152 B/op 26963 allocs/op BenchmarkPossum_GithubAll 8113 263665 ns/op 84448 B/op 609 allocs/op BenchmarkR2router_GithubAll 7172 247198 ns/op 77328 B/op 979 allocs/op BenchmarkRivet_GithubAll 10000 128086 ns/op 16272 B/op 167 allocs/op BenchmarkTango_GithubAll 3316 472753 ns/op 63825 B/op 1618 allocs/op BenchmarkTigerTonic_GithubAll 1176 1041991 ns/op 193856 B/op 4474 allocs/op BenchmarkTraffic_GithubAll 226 5312082 ns/op 820742 B/op 14114 allocs/op BenchmarkVulcan_GithubAll 3904 304440 ns/op 19894 B/op 609 allocs/op ``` ## Google+ ``` BenchmarkGin_GPlusStatic 9172405 124 ns/op 0 B/op 0 allocs/op BenchmarkAce_GPlusStatic 7784710 152 ns/op 0 B/op 0 allocs/op BenchmarkAero_GPlusStatic 12771894 89.2 ns/op 0 B/op 0 allocs/op BenchmarkBear_GPlusStatic 2351325 512 ns/op 104 B/op 3 allocs/op BenchmarkBeego_GPlusStatic 1000000 1643 ns/op 352 B/op 3 allocs/op BenchmarkBone_GPlusStatic 4419217 263 ns/op 32 B/op 1 allocs/op BenchmarkChi_GPlusStatic 1000000 1282 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_GPlusStatic 17730754 61.9 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GPlusStatic 29549895 38.3 ns/op 0 B/op 0 allocs/op BenchmarkEcho_GPlusStatic 10521789 111 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlusStatic 1000000 1053 ns/op 280 B/op 5 allocs/op BenchmarkGoji_GPlusStatic 5209968 228 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_GPlusStatic 306363 3348 ns/op 1312 B/op 10 allocs/op BenchmarkGoJsonRest_GPlusStatic 1000000 1424 ns/op 329 B/op 11 allocs/op BenchmarkGoRestful_GPlusStatic 130754 8760 ns/op 3872 B/op 13 allocs/op BenchmarkGorillaMux_GPlusStatic 496250 2860 ns/op 976 B/op 9 allocs/op BenchmarkGowwwRouter_GPlusStatic 16401519 66.5 ns/op 0 B/op 0 allocs/op BenchmarkHttpRouter_GPlusStatic 21323139 50.3 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_GPlusStatic 14877926 68.7 ns/op 0 B/op 0 allocs/op BenchmarkKocha_GPlusStatic 18375128 57.6 ns/op 0 B/op 0 allocs/op BenchmarkLARS_GPlusStatic 11153810 101 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusStatic 652598 2720 ns/op 736 B/op 8 allocs/op BenchmarkMartini_GPlusStatic 218824 6532 ns/op 768 B/op 9 allocs/op BenchmarkPat_GPlusStatic 2825560 428 ns/op 96 B/op 2 allocs/op BenchmarkPossum_GPlusStatic 1000000 1236 ns/op 416 B/op 3 allocs/op BenchmarkR2router_GPlusStatic 2222193 541 ns/op 144 B/op 4 allocs/op BenchmarkRivet_GPlusStatic 9802023 114 ns/op 0 B/op 0 allocs/op BenchmarkTango_GPlusStatic 980658 1465 ns/op 200 B/op 8 allocs/op BenchmarkTigerTonic_GPlusStatic 4882701 239 ns/op 32 B/op 1 allocs/op BenchmarkTraffic_GPlusStatic 508060 3465 ns/op 1112 B/op 16 allocs/op BenchmarkVulcan_GPlusStatic 1608979 725 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlusParam 2962957 414 ns/op 64 B/op 1 allocs/op BenchmarkAero_GPlusParam 5667668 202 ns/op 0 B/op 0 allocs/op BenchmarkBear_GPlusParam 1000000 1271 ns/op 480 B/op 5 allocs/op BenchmarkBeego_GPlusParam 869858 1874 ns/op 352 B/op 3 allocs/op BenchmarkBone_GPlusParam 869476 2395 ns/op 816 B/op 6 allocs/op BenchmarkChi_GPlusParam 1000000 1469 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_GPlusParam 11149783 108 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GPlusParam 4007298 301 ns/op 64 B/op 1 allocs/op BenchmarkEcho_GPlusParam 6448201 174 ns/op 0 B/op 0 allocs/op BenchmarkGin_GPlusParam 5470827 218 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlusParam 1000000 1939 ns/op 648 B/op 8 allocs/op BenchmarkGoji_GPlusParam 1207621 997 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GPlusParam 271326 4013 ns/op 1328 B/op 11 allocs/op BenchmarkGoJsonRest_GPlusParam 781062 2303 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_GPlusParam 121267 9871 ns/op 4192 B/op 14 allocs/op BenchmarkGorillaMux_GPlusParam 228406 5156 ns/op 1280 B/op 10 allocs/op BenchmarkGowwwRouter_GPlusParam 1000000 1074 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_GPlusParam 4399740 276 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_GPlusParam 1309540 898 ns/op 352 B/op 3 allocs/op BenchmarkKocha_GPlusParam 2930965 403 ns/op 56 B/op 3 allocs/op BenchmarkLARS_GPlusParam 7588237 151 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusParam 434997 4195 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_GPlusParam 148207 8144 ns/op 1072 B/op 10 allocs/op BenchmarkPat_GPlusParam 566829 2533 ns/op 576 B/op 11 allocs/op BenchmarkPossum_GPlusParam 1000000 1723 ns/op 496 B/op 5 allocs/op BenchmarkR2router_GPlusParam 1000000 1100 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GPlusParam 3309052 331 ns/op 48 B/op 1 allocs/op BenchmarkTango_GPlusParam 693728 1825 ns/op 264 B/op 8 allocs/op BenchmarkTigerTonic_GPlusParam 417693 3800 ns/op 856 B/op 16 allocs/op BenchmarkTraffic_GPlusParam 179424 6641 ns/op 1872 B/op 21 allocs/op BenchmarkVulcan_GPlusParam 1000000 1063 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlus2Params 2720149 460 ns/op 64 B/op 1 allocs/op BenchmarkAero_GPlus2Params 3525165 343 ns/op 0 B/op 0 allocs/op BenchmarkBear_GPlus2Params 1000000 1502 ns/op 496 B/op 5 allocs/op BenchmarkBeego_GPlus2Params 730123 2102 ns/op 352 B/op 3 allocs/op BenchmarkBone_GPlus2Params 253177 5583 ns/op 1168 B/op 10 allocs/op BenchmarkChi_GPlus2Params 1000000 1531 ns/op 432 B/op 3 allocs/op BenchmarkCloudyKitRouter_GPlus2Params 6943176 168 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GPlus2Params 2912601 413 ns/op 64 B/op 1 allocs/op BenchmarkEcho_GPlus2Params 4149189 278 ns/op 0 B/op 0 allocs/op BenchmarkGin_GPlus2Params 3271269 356 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlus2Params 915531 2321 ns/op 712 B/op 9 allocs/op BenchmarkGoji_GPlus2Params 1000000 1413 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GPlus2Params 256640 4521 ns/op 1408 B/op 14 allocs/op BenchmarkGoJsonRest_GPlus2Params 499140 3076 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_GPlus2Params 105928 10148 ns/op 4384 B/op 16 allocs/op BenchmarkGorillaMux_GPlus2Params 110953 9682 ns/op 1296 B/op 10 allocs/op BenchmarkGowwwRouter_GPlus2Params 1000000 1112 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_GPlus2Params 3491893 321 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_GPlus2Params 1000000 1341 ns/op 384 B/op 4 allocs/op BenchmarkKocha_GPlus2Params 1445288 790 ns/op 128 B/op 5 allocs/op BenchmarkLARS_GPlus2Params 6644953 185 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlus2Params 424291 4321 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_GPlus2Params 70866 16407 ns/op 1200 B/op 13 allocs/op BenchmarkPat_GPlus2Params 121308 10221 ns/op 2168 B/op 33 allocs/op BenchmarkPossum_GPlus2Params 1000000 1847 ns/op 496 B/op 5 allocs/op BenchmarkR2router_GPlus2Params 1000000 1267 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GPlus2Params 2017526 590 ns/op 96 B/op 1 allocs/op BenchmarkTango_GPlus2Params 846003 2143 ns/op 344 B/op 8 allocs/op BenchmarkTigerTonic_GPlus2Params 303597 5736 ns/op 1200 B/op 22 allocs/op BenchmarkTraffic_GPlus2Params 95032 12817 ns/op 2248 B/op 28 allocs/op BenchmarkVulcan_GPlus2Params 692610 1575 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlusAll 271720 4948 ns/op 640 B/op 11 allocs/op BenchmarkAero_GPlusAll 367956 2926 ns/op 0 B/op 0 allocs/op BenchmarkBear_GPlusAll 68161 17883 ns/op 5488 B/op 61 allocs/op BenchmarkBeego_GPlusAll 46634 25369 ns/op 4576 B/op 39 allocs/op BenchmarkBone_GPlusAll 24628 49198 ns/op 11744 B/op 109 allocs/op BenchmarkChi_GPlusAll 60778 19356 ns/op 5616 B/op 39 allocs/op BenchmarkCloudyKitRouter_GPlusAll 706952 1693 ns/op 0 B/op 0 allocs/op BenchmarkDenco_GPlusAll 327422 4222 ns/op 672 B/op 11 allocs/op BenchmarkEcho_GPlusAll 331987 3176 ns/op 0 B/op 0 allocs/op BenchmarkGin_GPlusAll 289526 3559 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlusAll 45805 26768 ns/op 8040 B/op 103 allocs/op BenchmarkGoji_GPlusAll 74786 14428 ns/op 3696 B/op 22 allocs/op BenchmarkGojiv2_GPlusAll 23822 50355 ns/op 17616 B/op 154 allocs/op BenchmarkGoJsonRest_GPlusAll 35280 32989 ns/op 8117 B/op 170 allocs/op BenchmarkGoRestful_GPlusAll 10000 129418 ns/op 55520 B/op 192 allocs/op BenchmarkGorillaMux_GPlusAll 15968 76492 ns/op 16112 B/op 128 allocs/op BenchmarkGowwwRouter_GPlusAll 100096 12644 ns/op 4752 B/op 33 allocs/op BenchmarkHttpRouter_GPlusAll 474584 3704 ns/op 640 B/op 11 allocs/op BenchmarkHttpTreeMux_GPlusAll 98506 12480 ns/op 4032 B/op 38 allocs/op BenchmarkKocha_GPlusAll 213709 7358 ns/op 976 B/op 43 allocs/op BenchmarkLARS_GPlusAll 466608 2363 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusAll 34136 35790 ns/op 9568 B/op 104 allocs/op BenchmarkMartini_GPlusAll 8911 124543 ns/op 14016 B/op 145 allocs/op BenchmarkPat_GPlusAll 17391 69198 ns/op 15264 B/op 271 allocs/op BenchmarkPossum_GPlusAll 66774 17004 ns/op 5408 B/op 39 allocs/op BenchmarkR2router_GPlusAll 79681 13996 ns/op 5040 B/op 63 allocs/op BenchmarkRivet_GPlusAll 258788 5344 ns/op 768 B/op 11 allocs/op BenchmarkTango_GPlusAll 46930 25591 ns/op 3656 B/op 104 allocs/op BenchmarkTigerTonic_GPlusAll 20768 58038 ns/op 11600 B/op 242 allocs/op BenchmarkTraffic_GPlusAll 10000 108031 ns/op 26248 B/op 341 allocs/op BenchmarkVulcan_GPlusAll 71826 15724 ns/op 1274 B/op 39 allocs/op ``` ## Parse.com ``` BenchmarkGin_ParseStatic 8683893 140 ns/op 0 B/op 0 allocs/op BenchmarkAce_ParseStatic 7255582 160 ns/op 0 B/op 0 allocs/op BenchmarkAero_ParseStatic 11960128 95.0 ns/op 0 B/op 0 allocs/op BenchmarkBear_ParseStatic 1791033 659 ns/op 120 B/op 3 allocs/op BenchmarkBeego_ParseStatic 937918 1688 ns/op 352 B/op 3 allocs/op BenchmarkBone_ParseStatic 1261682 949 ns/op 144 B/op 3 allocs/op BenchmarkChi_ParseStatic 1000000 1303 ns/op 432 B/op 3 allocs/op BenchmarkDenco_ParseStatic 23731242 49.8 ns/op 0 B/op 0 allocs/op BenchmarkEcho_ParseStatic 10585060 116 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParseStatic 1000000 1156 ns/op 296 B/op 5 allocs/op BenchmarkGoji_ParseStatic 3927530 300 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_ParseStatic 474836 3281 ns/op 1312 B/op 10 allocs/op BenchmarkGoJsonRest_ParseStatic 1000000 1445 ns/op 329 B/op 11 allocs/op BenchmarkGoRestful_ParseStatic 101262 11612 ns/op 4256 B/op 13 allocs/op BenchmarkGorillaMux_ParseStatic 562705 3530 ns/op 976 B/op 9 allocs/op BenchmarkGowwwRouter_ParseStatic 16479007 69.5 ns/op 0 B/op 0 allocs/op BenchmarkHttpRouter_ParseStatic 23205590 51.5 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_ParseStatic 10763127 106 ns/op 0 B/op 0 allocs/op BenchmarkKocha_ParseStatic 17850259 60.9 ns/op 0 B/op 0 allocs/op BenchmarkLARS_ParseStatic 10727432 108 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseStatic 685586 2665 ns/op 736 B/op 8 allocs/op BenchmarkMartini_ParseStatic 200642 7158 ns/op 768 B/op 9 allocs/op BenchmarkPat_ParseStatic 1000000 1139 ns/op 240 B/op 5 allocs/op BenchmarkPossum_ParseStatic 1000000 1241 ns/op 416 B/op 3 allocs/op BenchmarkR2router_ParseStatic 2035426 597 ns/op 144 B/op 4 allocs/op BenchmarkRivet_ParseStatic 9707011 127 ns/op 0 B/op 0 allocs/op BenchmarkTango_ParseStatic 910617 1693 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_ParseStatic 3168885 385 ns/op 48 B/op 1 allocs/op BenchmarkTraffic_ParseStatic 493339 4264 ns/op 1256 B/op 19 allocs/op BenchmarkVulcan_ParseStatic 1394142 848 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParseParam 3106903 387 ns/op 64 B/op 1 allocs/op BenchmarkAero_ParseParam 8045266 141 ns/op 0 B/op 0 allocs/op BenchmarkBear_ParseParam 1000000 1434 ns/op 467 B/op 5 allocs/op BenchmarkBeego_ParseParam 951460 1937 ns/op 352 B/op 3 allocs/op BenchmarkBone_ParseParam 855555 2776 ns/op 896 B/op 7 allocs/op BenchmarkChi_ParseParam 1000000 1457 ns/op 432 B/op 3 allocs/op BenchmarkDenco_ParseParam 4084116 301 ns/op 64 B/op 1 allocs/op BenchmarkEcho_ParseParam 8440170 142 ns/op 0 B/op 0 allocs/op BenchmarkGin_ParseParam 7716948 157 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParseParam 886284 2045 ns/op 664 B/op 8 allocs/op BenchmarkGoji_ParseParam 1000000 1167 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_ParseParam 269731 3945 ns/op 1360 B/op 12 allocs/op BenchmarkGoJsonRest_ParseParam 719587 2277 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_ParseParam 96408 11925 ns/op 4576 B/op 14 allocs/op BenchmarkGorillaMux_ParseParam 289303 4154 ns/op 1280 B/op 10 allocs/op BenchmarkGowwwRouter_ParseParam 1000000 1070 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_ParseParam 4917758 232 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_ParseParam 1445443 828 ns/op 352 B/op 3 allocs/op BenchmarkKocha_ParseParam 3116233 382 ns/op 56 B/op 3 allocs/op BenchmarkLARS_ParseParam 10584750 113 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseParam 413617 3872 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_ParseParam 166545 7605 ns/op 1072 B/op 10 allocs/op BenchmarkPat_ParseParam 491829 3394 ns/op 992 B/op 15 allocs/op BenchmarkPossum_ParseParam 1000000 1692 ns/op 496 B/op 5 allocs/op BenchmarkR2router_ParseParam 1000000 1059 ns/op 432 B/op 5 allocs/op BenchmarkRivet_ParseParam 3572359 311 ns/op 48 B/op 1 allocs/op BenchmarkTango_ParseParam 787552 1889 ns/op 280 B/op 8 allocs/op BenchmarkTigerTonic_ParseParam 487208 3706 ns/op 784 B/op 15 allocs/op BenchmarkTraffic_ParseParam 186190 5812 ns/op 1896 B/op 21 allocs/op BenchmarkVulcan_ParseParam 1275432 892 ns/op 98 B/op 3 allocs/op BenchmarkAce_Parse2Params 2959621 412 ns/op 64 B/op 1 allocs/op BenchmarkAero_Parse2Params 6208641 192 ns/op 0 B/op 0 allocs/op BenchmarkBear_Parse2Params 1000000 1512 ns/op 496 B/op 5 allocs/op BenchmarkBeego_Parse2Params 761940 1973 ns/op 352 B/op 3 allocs/op BenchmarkBone_Parse2Params 715987 2582 ns/op 848 B/op 6 allocs/op BenchmarkChi_Parse2Params 1000000 1495 ns/op 432 B/op 3 allocs/op BenchmarkDenco_Parse2Params 3585452 341 ns/op 64 B/op 1 allocs/op BenchmarkEcho_Parse2Params 5193693 204 ns/op 0 B/op 0 allocs/op BenchmarkGin_Parse2Params 5338316 236 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Parse2Params 939637 2299 ns/op 712 B/op 9 allocs/op BenchmarkGoji_Parse2Params 1000000 1094 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Parse2Params 339514 3733 ns/op 1344 B/op 11 allocs/op BenchmarkGoJsonRest_Parse2Params 512572 2733 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_Parse2Params 95913 12973 ns/op 4928 B/op 14 allocs/op BenchmarkGorillaMux_Parse2Params 261208 4758 ns/op 1296 B/op 10 allocs/op BenchmarkGowwwRouter_Parse2Params 1000000 1084 ns/op 432 B/op 3 allocs/op BenchmarkHttpRouter_Parse2Params 4399953 277 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_Parse2Params 1000000 1198 ns/op 384 B/op 4 allocs/op BenchmarkKocha_Parse2Params 1669431 683 ns/op 128 B/op 5 allocs/op BenchmarkLARS_Parse2Params 8535754 142 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Parse2Params 424590 3959 ns/op 1072 B/op 10 allocs/op BenchmarkMartini_Parse2Params 162448 8141 ns/op 1152 B/op 11 allocs/op BenchmarkPat_Parse2Params 431336 3484 ns/op 752 B/op 16 allocs/op BenchmarkPossum_Parse2Params 1000000 1721 ns/op 496 B/op 5 allocs/op BenchmarkR2router_Parse2Params 1000000 1136 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Parse2Params 2630935 442 ns/op 96 B/op 1 allocs/op BenchmarkTango_Parse2Params 759218 1876 ns/op 312 B/op 8 allocs/op BenchmarkTigerTonic_Parse2Params 290810 5558 ns/op 1168 B/op 22 allocs/op BenchmarkTraffic_Parse2Params 181099 6917 ns/op 1944 B/op 22 allocs/op BenchmarkVulcan_Parse2Params 1000000 1080 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParseAll 162906 7888 ns/op 640 B/op 16 allocs/op BenchmarkAero_ParseAll 219260 4833 ns/op 0 B/op 0 allocs/op BenchmarkBear_ParseAll 37566 32863 ns/op 8928 B/op 110 allocs/op BenchmarkBeego_ParseAll 25400 46518 ns/op 9152 B/op 78 allocs/op BenchmarkBone_ParseAll 19568 61814 ns/op 16208 B/op 147 allocs/op BenchmarkChi_ParseAll 30562 38281 ns/op 11232 B/op 78 allocs/op BenchmarkDenco_ParseAll 232554 6371 ns/op 928 B/op 16 allocs/op BenchmarkEcho_ParseAll 224400 5090 ns/op 0 B/op 0 allocs/op BenchmarkGin_ParseAll 189829 6134 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParseAll 25446 47000 ns/op 13728 B/op 181 allocs/op BenchmarkGoji_ParseAll 50503 22949 ns/op 5376 B/op 32 allocs/op BenchmarkGojiv2_ParseAll 12806 93106 ns/op 34448 B/op 277 allocs/op BenchmarkGoJsonRest_ParseAll 20764 57021 ns/op 13866 B/op 321 allocs/op BenchmarkGoRestful_ParseAll 4234 317238 ns/op 117600 B/op 354 allocs/op BenchmarkGorillaMux_ParseAll 10000 146942 ns/op 30288 B/op 250 allocs/op BenchmarkGowwwRouter_ParseAll 62548 19363 ns/op 6912 B/op 48 allocs/op BenchmarkHttpRouter_ParseAll 286662 5091 ns/op 640 B/op 16 allocs/op BenchmarkHttpTreeMux_ParseAll 66952 18262 ns/op 5728 B/op 51 allocs/op BenchmarkKocha_ParseAll 109771 9811 ns/op 1112 B/op 54 allocs/op BenchmarkLARS_ParseAll 272516 3976 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseAll 17094 71634 ns/op 19136 B/op 208 allocs/op BenchmarkMartini_ParseAll 6799 208122 ns/op 25072 B/op 253 allocs/op BenchmarkPat_ParseAll 15993 74594 ns/op 15216 B/op 308 allocs/op BenchmarkPossum_ParseAll 34897 33398 ns/op 10816 B/op 78 allocs/op BenchmarkR2router_ParseAll 46909 25410 ns/op 8352 B/op 120 allocs/op BenchmarkRivet_ParseAll 185193 7725 ns/op 912 B/op 16 allocs/op BenchmarkTango_ParseAll 24481 47963 ns/op 7168 B/op 208 allocs/op BenchmarkTigerTonic_ParseAll 15236 79623 ns/op 16048 B/op 332 allocs/op BenchmarkTraffic_ParseAll 8955 169411 ns/op 45520 B/op 605 allocs/op BenchmarkVulcan_ParseAll 40406 28971 ns/op 2548 B/op 78 allocs/op ``` gin-1.6.3/CHANGELOG.md000066400000000000000000000576361365354716400141460ustar00rootroot00000000000000# Gin ChangeLog ## Gin v1.6.3 ### ENHANCEMENTS * Improve performance: Change `*sync.RWMutex` to `sync.RWMutex` in context. [#2351](https://github.com/gin-gonic/gin/pull/2351) ## Gin v1.6.2 ### BUFIXES * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) ### ENHANCEMENTS * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) ## Gin v1.6.1 ### BUFIXES * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) ## Gin v1.6.0 ### BREAKING * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159) * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) ### FEATURES * add yaml negotitation [#2220](https://github.com/gin-gonic/gin/pull/2220) * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) ### BUGFIXES * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228) * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216) * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166) * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121) * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391) ### ENHANCEMENTS * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277) * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229) * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222) * chore: upgrade go-isatty and json-iterator/go [#2215](https://github.com/gin-gonic/gin/pull/2215) * path: sync code with httprouter [#2212](https://github.com/gin-gonic/gin/pull/2212) * Use zero-copy approach to convert types between string and byte slice [#2206](https://github.com/gin-gonic/gin/pull/2206) * Reuse bytes when cleaning the URL paths [#2179](https://github.com/gin-gonic/gin/pull/2179) * tree: remove one else statement [#2177](https://github.com/gin-gonic/gin/pull/2177) * tree: sync httprouter update (#2173) (#2172) [#2171](https://github.com/gin-gonic/gin/pull/2171) * tree: sync part httprouter codes and reduce if/else [#2163](https://github.com/gin-gonic/gin/pull/2163) * use http method constant [#2155](https://github.com/gin-gonic/gin/pull/2155) * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149) * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970) * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852) ### DOCS * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223) * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217) * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202) * Remove broken link from README. [#2198](https://github.com/gin-gonic/gin/pull/2198) * Update docs on Context.Done(), Context.Deadline() and Context.Err() [#2196](https://github.com/gin-gonic/gin/pull/2196) * Update validator to v10 [#2190](https://github.com/gin-gonic/gin/pull/2190) * upgrade go-validator to v10 for README [#2189](https://github.com/gin-gonic/gin/pull/2189) * Update to currently output [#2188](https://github.com/gin-gonic/gin/pull/2188) * Fix "Custom Validators" example [#2186](https://github.com/gin-gonic/gin/pull/2186) * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165) * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153) * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122) ### MISC * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262) * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231) * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147) * fix comment in `mode.go` [#2129](https://github.com/gin-gonic/gin/pull/2129) ## Gin v1.5.0 - [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891) - [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893) - [FIX] Some code improvements [#1909](https://github.com/gin-gonic/gin/pull/1909) - [FIX] Use encode replace json marshal increase json encoder speed [#1546](https://github.com/gin-gonic/gin/pull/1546) - [NEW] Hold matched route full path in the Context [#1826](https://github.com/gin-gonic/gin/pull/1826) - [FIX] Fix context.Params race condition on Copy() [#1841](https://github.com/gin-gonic/gin/pull/1841) - [NEW] Add context param query cache [#1450](https://github.com/gin-gonic/gin/pull/1450) - [FIX] Improve GetQueryMap performance [#1918](https://github.com/gin-gonic/gin/pull/1918) - [FIX] Improve get post data [#1920](https://github.com/gin-gonic/gin/pull/1920) - [FIX] Use context instead of x/net/context [#1922](https://github.com/gin-gonic/gin/pull/1922) - [FIX] Attempt to fix PostForm cache bug [#1931](https://github.com/gin-gonic/gin/pull/1931) - [NEW] Add support of multipart multi files [#1949](https://github.com/gin-gonic/gin/pull/1949) - [NEW] Support bind http header param [#1957](https://github.com/gin-gonic/gin/pull/1957) - [FIX] Drop support for go1.8 and go1.9 [#1933](https://github.com/gin-gonic/gin/pull/1933) - [FIX] Bugfix for the FullPath feature [#1919](https://github.com/gin-gonic/gin/pull/1919) - [FIX] Gin1.5 bytes.Buffer to strings.Builder [#1939](https://github.com/gin-gonic/gin/pull/1939) - [FIX] Upgrade github.com/ugorji/go/codec [#1969](https://github.com/gin-gonic/gin/pull/1969) - [NEW] Support bind unix time [#1980](https://github.com/gin-gonic/gin/pull/1980) - [FIX] Simplify code [#2004](https://github.com/gin-gonic/gin/pull/2004) - [NEW] Support negative Content-Length in DataFromReader [#1981](https://github.com/gin-gonic/gin/pull/1981) - [FIX] Identify terminal on a RISC-V architecture for auto-colored logs [#2019](https://github.com/gin-gonic/gin/pull/2019) - [BREAKING] `Context.JSONP()` now expects a semicolon (`;`) at the end [#2007](https://github.com/gin-gonic/gin/pull/2007) - [BREAKING] Upgrade default `binding.Validator` to v9 (see [its changelog](https://github.com/go-playground/validator/releases/tag/v9.0.0)) [#1015](https://github.com/gin-gonic/gin/pull/1015) - [NEW] Add `DisallowUnknownFields()` in `Context.BindJSON()` [#2028](https://github.com/gin-gonic/gin/pull/2028) - [NEW] Use specific `net.Listener` with `Engine.RunListener()` [#2023](https://github.com/gin-gonic/gin/pull/2023) - [FIX] Fix some typo [#2079](https://github.com/gin-gonic/gin/pull/2079) [#2080](https://github.com/gin-gonic/gin/pull/2080) - [FIX] Relocate binding body tests [#2086](https://github.com/gin-gonic/gin/pull/2086) - [FIX] Use Writer in Context.Status [#1606](https://github.com/gin-gonic/gin/pull/1606) - [FIX] `Engine.RunUnix()` now returns the error if it can't change the file mode [#2093](https://github.com/gin-gonic/gin/pull/2093) - [FIX] `RouterGroup.StaticFS()` leaked files. Now it closes them. [#2118](https://github.com/gin-gonic/gin/pull/2118) - [FIX] `Context.Request.FormFile` leaked file. Now it closes it. [#2114](https://github.com/gin-gonic/gin/pull/2114) - [FIX] Ignore walking on `form:"-"` mapping [#1943](https://github.com/gin-gonic/gin/pull/1943) ### Gin v1.4.0 - [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) - [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829) - [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830) - [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802) - [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264) - [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797) - [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789) - [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804) - [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779) - [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791) - [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794) - [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749) - [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252) - [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775) - [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260) - [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112) - [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238) - [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020) - [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729) - [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771) - [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722) - [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752) - [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733) - [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724) - [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745) - [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653) - [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690) - [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694) - [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677) - [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669) - [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663) - [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638) - [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612) - [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640) - [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650) - [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259) - [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609) - [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618) - [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600) - [FIX] LoadHTML* tests [#1559](https://github.com/gin-gonic/gin/pull/1559) - [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565) - [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571) - [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570) - [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370) - [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560) - [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694) - [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497) - [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498) - [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496) - [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479) - [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487) - [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485) - [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491) ## Gin v1.3.0 - [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) - [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) - [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273) - [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304) - [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341) - [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336) - [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333) - [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138) - [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277) - [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047) - [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117) - [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029) - [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026) - [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999) - [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993) - [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie) - [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072) - [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250) - [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460) ## Gin 1.2.0 - [NEW] Switch from godeps to govendor - [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine - [NEW] Add custom template delimiters, see #860 - [NEW] Add Template Func Maps, see #962 - [NEW] Add \*context.Handler(), see #928 - [NEW] Add \*context.GetRawData() - [NEW] Add \*context.GetHeader() (request) - [NEW] Add \*context.AbortWithStatusJSON() (JSON content type) - [NEW] Add \*context.Keys type cast helpers - [NEW] Add \*context.ShouldBindWith() - [NEW] Add \*context.MustBindWith() - [NEW] Add \*engine.SetFuncMap() - [DEPRECATE] On next release: \*context.BindWith(), see #855 - [FIX] Refactor render - [FIX] Reworked tests - [FIX] logger now supports cygwin - [FIX] Use X-Forwarded-For before X-Real-Ip - [FIX] time.Time binding (#904) ## Gin 1.1.4 - [NEW] Support google appengine for IsTerminal func ## Gin 1.1.3 - [FIX] Reverted Logger: skip ANSI color commands ## Gin 1.1 - [NEW] Implement QueryArray and PostArray methods - [NEW] Refactor GetQuery and GetPostForm - [NEW] Add contribution guide - [FIX] Corrected typos in README - [FIX] Removed additional Iota - [FIX] Changed imports to gopkg instead of github in README (#733) - [FIX] Logger: skip ANSI color commands if output is not a tty ## Gin 1.0rc2 (...) - [PERFORMANCE] Fast path for writing Content-Type. - [PERFORMANCE] Much faster 404 routing - [PERFORMANCE] Allocation optimizations - [PERFORMANCE] Faster root tree lookup - [PERFORMANCE] Zero overhead, String() and JSON() rendering. - [PERFORMANCE] Faster ClientIP parsing - [PERFORMANCE] Much faster SSE implementation - [NEW] Benchmarks suite - [NEW] Bind validation can be disabled and replaced with custom validators. - [NEW] More flexible HTML render - [NEW] Multipart and PostForm bindings - [NEW] Adds method to return all the registered routes - [NEW] Context.HandlerName() returns the main handler's name - [NEW] Adds Error.IsType() helper - [FIX] Binding multipart form - [FIX] Integration tests - [FIX] Crash when binding non struct object in Context. - [FIX] RunTLS() implementation - [FIX] Logger() unit tests - [FIX] Adds SetHTMLTemplate() warning - [FIX] Context.IsAborted() - [FIX] More unit tests - [FIX] JSON, XML, HTML renders accept custom content-types - [FIX] gin.AbortIndex is unexported - [FIX] Better approach to avoid directory listing in StaticFS() - [FIX] Context.ClientIP() always returns the IP with trimmed spaces. - [FIX] Better warning when running in debug mode. - [FIX] Google App Engine integration. debugPrint does not use os.Stdout - [FIX] Fixes integer overflow in error type - [FIX] Error implements the json.Marshaller interface - [FIX] MIT license in every file ## Gin 1.0rc1 (May 22, 2015) - [PERFORMANCE] Zero allocation router - [PERFORMANCE] Faster JSON, XML and text rendering - [PERFORMANCE] Custom hand optimized HttpRouter for Gin - [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations - [NEW] Built-in support for golang.org/x/net/context - [NEW] Any(path, handler). Create a route that matches any path - [NEW] Refactored rendering pipeline (faster and static typed) - [NEW] Refactored errors API - [NEW] IndentedJSON() prints pretty JSON - [NEW] Added gin.DefaultWriter - [NEW] UNIX socket support - [NEW] RouterGroup.BasePath is exposed - [NEW] JSON validation using go-validate-yourself (very powerful options) - [NEW] Completed suite of unit tests - [NEW] HTTP streaming with c.Stream() - [NEW] StaticFile() creates a router for serving just one file. - [NEW] StaticFS() has an option to disable directory listing. - [NEW] StaticFS() for serving static files through virtual filesystems - [NEW] Server-Sent Events native support - [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler - [NEW] Added LoggerWithWriter() middleware - [NEW] Added RecoveryWithWriter() middleware - [NEW] Added DefaultPostFormValue() - [NEW] Added DefaultFormValue() - [NEW] Added DefaultParamValue() - [FIX] BasicAuth() when using custom realm - [FIX] Bug when serving static files in nested routing group - [FIX] Redirect using built-in http.Redirect() - [FIX] Logger when printing the requested path - [FIX] Documentation typos - [FIX] Context.Engine renamed to Context.engine - [FIX] Better debugging messages - [FIX] ErrorLogger - [FIX] Debug HTTP render - [FIX] Refactored binding and render modules - [FIX] Refactored Context initialization - [FIX] Refactored BasicAuth() - [FIX] NoMethod/NoRoute handlers - [FIX] Hijacking http - [FIX] Better support for Google App Engine (using log instead of fmt) ## Gin 0.6 (Mar 9, 2015) - [NEW] Support multipart/form-data - [NEW] NoMethod handler - [NEW] Validate sub structures - [NEW] Support for HTTP Realm Auth - [FIX] Unsigned integers in binding - [FIX] Improve color logger ## Gin 0.5 (Feb 7, 2015) - [NEW] Content Negotiation - [FIX] Solved security bug that allow a client to spoof ip - [FIX] Fix unexported/ignored fields in binding ## Gin 0.4 (Aug 21, 2014) - [NEW] Development mode - [NEW] Unit tests - [NEW] Add Content.Redirect() - [FIX] Deferring WriteHeader() - [FIX] Improved documentation for model binding ## Gin 0.3 (Jul 18, 2014) - [PERFORMANCE] Normal log and error log are printed in the same call. - [PERFORMANCE] Improve performance of NoRouter() - [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults. - [NEW] Flexible rendering API - [NEW] Add Context.File() - [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS - [FIX] Rename NotFound404() to NoRoute() - [FIX] Errors in context are purged - [FIX] Adds HEAD method in Static file serving - [FIX] Refactors Static() file serving - [FIX] Using keyed initialization to fix app-engine integration - [FIX] Can't unmarshal JSON array, #63 - [FIX] Renaming Context.Req to Context.Request - [FIX] Check application/x-www-form-urlencoded when parsing form ## Gin 0.2b (Jul 08, 2014) - [PERFORMANCE] Using sync.Pool to allocatio/gc overhead - [NEW] Travis CI integration - [NEW] Completely new logger - [NEW] New API for serving static files. gin.Static() - [NEW] gin.H() can be serialized into XML - [NEW] Typed errors. Errors can be typed. Internet/external/custom. - [NEW] Support for Godeps - [NEW] Travis/Godocs badges in README - [NEW] New Bind() and BindWith() methods for parsing request body. - [NEW] Add Content.Copy() - [NEW] Add context.LastError() - [NEW] Add shortcut for OPTIONS HTTP method - [FIX] Tons of README fixes - [FIX] Header is written before body - [FIX] BasicAuth() and changes API a little bit - [FIX] Recovery() middleware only prints panics - [FIX] Context.Get() does not panic anymore. Use MustGet() instead. - [FIX] Multiple http.WriteHeader() in NotFound handlers - [FIX] Engine.Run() panics if http server can't be set up - [FIX] Crash when route path doesn't start with '/' - [FIX] Do not update header when status code is negative - [FIX] Setting response headers before calling WriteHeader in context.String() - [FIX] Add MIT license - [FIX] Changes behaviour of ErrorLogger() and Logger() gin-1.6.3/CODE_OF_CONDUCT.md000066400000000000000000000062231365354716400151160ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ gin-1.6.3/CONTRIBUTING.md000066400000000000000000000011571365354716400145510ustar00rootroot00000000000000## Contributing - With issues: - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. gin-1.6.3/LICENSE000066400000000000000000000021031365354716400133150ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gin-1.6.3/Makefile000066400000000000000000000033551365354716400137620ustar00rootroot00000000000000GO ?= go GOFMT ?= gofmt "-s" 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) TESTTAGS ?= "" .PHONY: test test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ exit 1; \ elif grep -q "build failed" tmp.out; then \ rm tmp.out; \ exit 1; \ elif grep -q "setup failed" tmp.out; then \ rm tmp.out; \ exit 1; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ rm profile.out; \ fi; \ done .PHONY: fmt fmt: $(GOFMT) -w $(GOFILES) .PHONY: fmt-check fmt-check: @diff=$$($(GOFMT) -d $(GOFILES)); \ if [ -n "$$diff" ]; then \ echo "Please run 'make fmt' and commit the result:"; \ echo "$${diff}"; \ exit 1; \ fi; vet: $(GO) vet $(VETPACKAGES) .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; .PHONY: misspell-check misspell-check: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -error $(GOFILES) .PHONY: misspell misspell: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) .PHONY: tools tools: go install golang.org/x/lint/golint; \ go install github.com/client9/misspell/cmd/misspell; gin-1.6.3/README.md000066400000000000000000001534441365354716400136060ustar00rootroot00000000000000# Gin Web Framework [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![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) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. ## Contents - [Gin Web Framework](#gin-web-framework) - [Contents](#contents) - [Installation](#installation) - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1. stable](#gin-v1-stable) - [Build with jsoniter](#build-with-jsoniter) - [API Examples](#api-examples) - [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) - [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Another example: query + post form](#another-example-query--post-form) - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - [Upload files](#upload-files) - [Single file](#single-file) - [Multiple files](#multiple-files) - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) - [Controlling Log output coloring](#controlling-log-output-coloring) - [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 Uri](#bind-uri) - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - [SecureJSON](#securejson) - [JSONP](#jsonp) - [AsciiJSON](#asciijson) - [PureJSON](#purejson) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) - [Custom Template renderer](#custom-template-renderer) - [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) - [Custom HTTP configuration](#custom-http-configuration) - [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) - [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) - [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) - [Testing](#testing) - [Users](#users) ## Installation To install Gin package, you need to install Go and set your Go workspace first. 1. The first need [Go](https://golang.org/) installed (**version 1.11+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin ``` 2. Import it in your code: ```go import "github.com/gin-gonic/gin" ``` 3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. ```go import "net/http" ``` ## Quick start ```sh # assume the following codes in example.go file $ cat example.go ``` ```go package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } ``` ``` # run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser $ go run example.go ``` ## Benchmarks Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) [See all benchmarks](/BENCHMARKS.md) Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: **BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0** BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167 BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203 BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686 BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334 BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712 BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737 BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519 BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272 BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167 BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671 BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843 BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0 BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000 BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325 BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435 BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609 BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979 BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167 BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618 BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 - (1): Total Repetitions achieved in constant time, higher means more confident result - (2): Single Repetition Duration (ns/op), lower is better - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better ## Gin v1. stable - [x] Zero allocation router. - [x] Still the fastest http router and framework. From routing to writing. - [x] Complete suite of unit tests - [x] Battle tested - [x] API frozen, new releases will not break your code. ## Build with [jsoniter](https://github.com/json-iterator/go) Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. ```sh $ go build -tags=jsoniter . ``` ## API Examples You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ```go func main() { // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options) // By default it serves on :8080 unless a // PORT environment variable was defined. router.Run() // router.Run(":3000") for a hard coded port } ``` ### Parameters in path ```go func main() { router := gin.Default() // This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) }) // However, this one will match /user/john/ and also /user/john/send // If no other routers match /user/john, it will redirect to /user/john/ router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) }) // For each matched request Context will hold the route definition router.POST("/user/:name/*action", func(c *gin.Context) { c.FullPath() == "/user/:name/*action" // true }) router.Run(":8080") } ``` ### Querystring parameters ```go func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") c.String(http.StatusOK, "Hello %s %s", firstname, lastname) }) router.Run(":8080") } ``` ### Multipart/Urlencoded Form ```go func main() { router := gin.Default() router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous") c.JSON(200, gin.H{ "status": "posted", "message": message, "nick": nick, }) }) router.Run(":8080") } ``` ### Another example: query + post form ``` POST /post?id=1234&page=1 HTTP/1.1 Content-Type: application/x-www-form-urlencoded name=manu&message=this_is_great ``` ```go func main() { router := gin.Default() router.POST("/post", func(c *gin.Context) { id := c.Query("id") page := c.DefaultQuery("page", "0") name := c.PostForm("name") message := c.PostForm("message") fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) }) router.Run(":8080") } ``` ``` id: 1234; page: 1; name: manu; message: this_is_great ``` ### Map as querystring or postform parameters ``` POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded names[first]=thinkerou&names[second]=tianou ``` ```go func main() { router := gin.Default() router.POST("/post", func(c *gin.Context) { ids := c.QueryMap("ids") names := c.PostFormMap("names") fmt.Printf("ids: %v; names: %v", ids, names) }) router.Run(":8080") } ``` ``` ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] ``` ### Upload files #### Single file References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). `file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) > The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. ```go func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // single file file, _ := c.FormFile("file") log.Println(file.Filename) // Upload the file to specific dst. // c.SaveUploadedFile(file, dst) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") } ``` How to `curl`: ```bash curl -X POST http://localhost:8080/upload \ -F "file=@/Users/appleboy/test.zip" \ -H "Content-Type: multipart/form-data" ``` #### Multiple files See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). ```go func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() files := form.File["upload[]"] for _, file := range files { log.Println(file.Filename) // Upload the file to specific dst. // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) router.Run(":8080") } ``` How to `curl`: ```bash curl -X POST http://localhost:8080/upload \ -F "upload[]=@/Users/appleboy/test1.zip" \ -F "upload[]=@/Users/appleboy/test2.zip" \ -H "Content-Type: multipart/form-data" ``` ### Grouping routes ```go func main() { router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") } ``` ### Blank Gin without middleware by default Use ```go r := gin.New() ``` instead of ```go // Default With the Logger and Recovery middleware already attached r := gin.Default() ``` ### Using middleware ```go func main() { // Creates a router without any middleware by default r := gin.New() // Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery()) // Per route middleware, you can add as many as you desire. r.GET("/benchmark", MyBenchLogger(), benchEndpoint) // Authorization group // authorized := r.Group("/", AuthRequired()) // exactly the same as: authorized := r.Group("/") // per group middleware! in this case we use the custom created // AuthRequired() middleware just in the "authorized" group. authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint) // nested group testing := authorized.Group("testing") testing.GET("/analytics", analyticsEndpoint) } // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` ### How to write log file ```go func main() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() // Logging to a file. f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f) // Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })    router.Run(":8080") } ``` ### Custom Log Format ```go func main() { router := gin.New() // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter // By default gin.DefaultWriter = os.Stdout router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { // your custom format return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", param.ClientIP, param.TimeStamp.Format(time.RFC1123), param.Method, param.Path, param.Request.Proto, param.StatusCode, param.Latency, param.Request.UserAgent(), param.ErrorMessage, ) })) router.Use(gin.Recovery()) router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) router.Run(":8080") } ``` **Sample Output** ``` ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` ### Controlling Log output coloring By default, logs output on console should be colorized depending on the detected TTY. Never colorize logs: ```go func main() { // Disable log's color gin.DisableConsoleColor() // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) router.Run(":8080") } ``` Always colorize logs: ```go func main() { // Force log's color gin.ForceConsoleColor() // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) router.Run(":8080") } ``` ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. Also, Gin provides two sets of methods for binding: - **Type** - Must bind - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. ```go // Binding from JSON type Login struct { User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } func main() { router := gin.Default() // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if json.User != "manu" || json.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding XML ( // // // user // 123 // ) router.POST("/loginXML", func(c *gin.Context) { var xml Login if err := c.ShouldBindXML(&xml); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if xml.User != "manu" || xml.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if form.User != "manu" || form.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Listen and serve on 0.0.0.0:8080 router.Run(":8080") } ``` **Sample request** ```shell $ curl -v -X POST \ http://localhost:8080/loginJSON \ -H 'content-type: application/json' \ -d '{ "user": "manu" }' > POST /loginJSON HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.51.0 > Accept: */* > content-type: application/json > Content-Length: 18 > * upload completely sent off: 18 out of 18 bytes < HTTP/1.1 400 Bad Request < Content-Type: application/json; charset=utf-8 < Date: Fri, 04 Aug 2017 03:51:31 GMT < Content-Length: 100 < {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` **Skip validate** When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. ### Custom Validators It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). ```go package main import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gopkg.in/go-playground/validator.v10" ) // Booking contains binded and validated data. type Booking struct { CheckIn time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` } var bookableDate validator.Func = func(fl validator.FieldLevel) bool { date, ok := fl.Field().Interface().(time.Time) if ok { today := time.Now() if today.After(date) { return false } } return true } func main() { route := gin.Default() if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("bookabledate", bookableDate) } route.GET("/bookable", getBookable) route.Run(":8085") } func getBookable(c *gin.Context) { var b Booking if err := c.ShouldBindWith(&b, binding.Query); err == nil { c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } } ``` ```console $ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" {"message":"Booking dates are valid!"} $ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09" {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} ``` [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. ### Only Bind Query String `ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). ```go package main import ( "log" "github.com/gin-gonic/gin" ) type Person struct { Name string `form:"name"` Address string `form:"address"` } func main() { route := gin.Default() route.Any("/testing", startPage) route.Run(":8085") } func startPage(c *gin.Context) { var person Person if c.ShouldBindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) } c.String(200, "Success") } ``` ### Bind Query String or Post Data See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). ```go package main import ( "log" "time" "github.com/gin-gonic/gin" ) type Person struct { Name string `form:"name"` Address string `form:"address"` 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") } 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 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) log.Println(person.CreateTime) log.Println(person.UnixTime) } c.String(200, "Success") } ``` Test it with: ```sh $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` ### Bind Uri See the [detail information](https://github.com/gin-gonic/gin/issues/846). ```go package main import "github.com/gin-gonic/gin" type Person struct { ID string `uri:"id" binding:"required,uuid"` Name string `uri:"name" binding:"required"` } func main() { route := gin.Default() route.GET("/:name/:id", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { c.JSON(400, gin.H{"msg": err}) return } c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) }) route.Run(":8088") } ``` Test it with: ```sh $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 $ curl -v localhost:8088/thinkerou/not-uuid ``` ### Bind Header ```go package main import ( "fmt" "github.com/gin-gonic/gin" ) type testHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` } func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { h := testHeader{} if err := c.ShouldBindHeader(&h); err != nil { c.JSON(200, err) } fmt.Printf("%#v\n", h) c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) }) r.Run() // client // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ // output // {"Domain":"music","Rate":300} } ``` ### Bind HTML checkboxes See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) main.go ```go ... type myForm struct { Colors []string `form:"colors[]"` } ... func formHandler(c *gin.Context) { var fakeForm myForm c.ShouldBind(&fakeForm) c.JSON(200, gin.H{"color": fakeForm.Colors}) } ... ``` form.html ```html

Check some colors

``` result: ``` {"color":["red","green","blue"]} ``` ### Multipart/Urlencoded binding ```go type ProfileForm struct { Name string `form:"name" binding:"required"` Avatar *multipart.FileHeader `form:"avatar" binding:"required"` // or for multiple files // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` } func main() { router := gin.Default() router.POST("/profile", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.ShouldBindWith(&form, binding.Form) // or you can simply use autobinding with ShouldBind method: var form ProfileForm // in this case proper binding will be automatically selected if err := c.ShouldBind(&form); err != nil { c.String(http.StatusBadRequest, "bad request") return } err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) if err != nil { c.String(http.StatusInternalServerError, "unknown error") return } // db.Save(&form) c.String(http.StatusOK, "ok") }) router.Run(":8080") } ``` Test it with: ```sh $ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` ### XML, JSON, YAML and ProtoBuf rendering ```go func main() { r := gin.Default() // gin.H is a shortcut for map[string]interface{} r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/moreJSON", func(c *gin.Context) { // You also can use a struct var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 // Note that msg.Name becomes "user" in the JSON // Will output : {"user": "Lena", "Message": "hey", "Number": 123} c.JSON(http.StatusOK, msg) }) r.GET("/someXML", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/someYAML", func(c *gin.Context) { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/someProtoBuf", func(c *gin.Context) { reps := []int64{int64(1), int64(2)} label := "test" // The specific definition of protobuf is written in the testdata/protoexample file. data := &protoexample.Test{ Label: &label, Reps: reps, } // Note that data becomes binary data in the response // Will output protoexample.Test protobuf serialized data c.ProtoBuf(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` #### SecureJSON Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. ```go func main() { r := gin.Default() // You can also use your own secure json prefix // r.SecureJsonPrefix(")]}',\n") r.GET("/someJSON", func(c *gin.Context) { names := []string{"lena", "austin", "foo"} // Will output : while(1);["lena","austin","foo"] c.SecureJSON(http.StatusOK, names) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` #### JSONP Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. ```go func main() { r := gin.Default() r.GET("/JSONP", func(c *gin.Context) { data := gin.H{ "foo": "bar", } //callback is x // Will output : x({\"foo\":\"bar\"}) c.JSONP(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") // client // curl http://127.0.0.1:8080/JSONP?callback=x } ``` #### AsciiJSON Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters. ```go func main() { r := gin.Default() r.GET("/someJSON", func(c *gin.Context) { data := gin.H{ "lang": "GO语言", "tag": "
", } // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} c.AsciiJSON(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` #### PureJSON Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. This feature is unavailable in Go 1.6 and lower. ```go func main() { r := gin.Default() // Serves unicode entities r.GET("/json", func(c *gin.Context) { c.JSON(200, gin.H{ "html": "Hello, world!", }) }) // Serves literal characters r.GET("/purejson", func(c *gin.Context) { c.PureJSON(200, gin.H{ "html": "Hello, world!", }) }) // listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` ### Serving static files ```go func main() { router := gin.Default() router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") // Listen and serve on 0.0.0.0:8080 router.Run(":8080") } ``` ### Serving data from file ```go func main() { router := gin.Default() router.GET("/local/file", func(c *gin.Context) { c.File("local/file.go") }) var fs http.FileSystem = // ... router.GET("/fs/file", func(c *gin.Context) { c.FileFromFS("fs/file.go", fs) }) } ``` ### Serving data from reader ```go func main() { router := gin.Default() router.GET("/someDataFromReader", func(c *gin.Context) { response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") if err != nil || response.StatusCode != http.StatusOK { c.Status(http.StatusServiceUnavailable) return } reader := response.Body contentLength := response.ContentLength contentType := response.Header.Get("Content-Type") extraHeaders := map[string]string{ "Content-Disposition": `attachment; filename="gopher.png"`, } c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) }) router.Run(":8080") } ``` ### HTML rendering Using LoadHTMLGlob() or LoadHTMLFiles() ```go func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", }) }) router.Run(":8080") } ``` templates/index.tmpl ```html

{{ .title }}

``` Using templates with same name in different directories ```go func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run(":8080") } ``` templates/posts/index.tmpl ```html {{ define "posts/index.tmpl" }}

{{ .title }}

Using posts/index.tmpl

{{ end }} ``` templates/users/index.tmpl ```html {{ define "users/index.tmpl" }}

{{ .title }}

Using users/index.tmpl

{{ end }} ``` #### Custom Template renderer You can also use your own html template render ```go import "html/template" func main() { router := gin.Default() html := template.Must(template.ParseFiles("file1", "file2")) router.SetHTMLTemplate(html) router.Run(":8080") } ``` #### Custom Delimiters You may use custom delims ```go r := gin.Default() r.Delims("{[{", "}]}") r.LoadHTMLGlob("/path/to/templates") ``` #### Custom Template Funcs See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). main.go ```go import ( "fmt" "html/template" "net/http" "time" "github.com/gin-gonic/gin" ) func formatAsDate(t time.Time) string { year, month, day := t.Date() return fmt.Sprintf("%d%02d/%02d", year, month, day) } func main() { router := gin.Default() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", gin.H{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) router.Run(":8080") } ``` raw.tmpl ```html Date: {[{.now | formatAsDate}]} ``` Result: ``` Date: 2017/07/01 ``` ### Multitemplate Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. ### 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 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(200, 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) } } 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") } ``` ### 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") } ``` ### Custom HTTP configuration Use `http.ListenAndServe()` directly, like this: ```go func main() { router := gin.Default() http.ListenAndServe(":8080", router) } ``` or ```go func main() { router := gin.Default() s := &http.Server{ Addr: ":8080", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() } ``` ### Support Let's Encrypt example for 1-line LetsEncrypt HTTPS servers. ```go package main import ( "log" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // Ping handler r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) log.Fatal(autotls.Run(r, "example1.com", "example2.com")) } ``` example for custom autocert manager. ```go package main import ( "log" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" "golang.org/x/crypto/acme/autocert" ) func main() { r := gin.Default() // Ping handler r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), Cache: autocert.DirCache("/var/www/.cache"), } log.Fatal(autotls.RunWithManager(r, &m)) } ``` ### Run multiple service using Gin See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: ```go package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" ) var ( g errgroup.Group ) func router01() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 01", }, ) }) return e } func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 02", }, ) }) return e } func main() { server01 := &http.Server{ Addr: ":8080", Handler: router01(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } server02 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } g.Go(func() error { err := server01.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Fatal(err) } return err }) g.Go(func() error { err := server02.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Fatal(err) } return err }) if err := g.Wait(); err != nil { log.Fatal(err) } } ``` ### Graceful shutdown or restart There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. #### Third-party packages We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. ```go router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router) ``` Alternatives: * [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. * [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. * [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. #### Manually In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). ```go // +build go1.8 package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") }) srv := &http.Server{ Addr: ":8080", Handler: router, } // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shuting down server...") // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } log.Println("Server exiting") } ``` ### Build a single binary with templates You can build a server into a single binary containing templates by using [go-assets][]. [go-assets]: https://github.com/jessevdk/go-assets ```go func main() { r := gin.New() t, err := loadTemplate() if err != nil { panic(err) } r.SetHTMLTemplate(t) r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "/html/index.tmpl",nil) }) r.Run(":8080") } // loadTemplate loads templates embedded by go-assets-builder func loadTemplate() (*template.Template, error) { t := template.New("") for name, file := range Assets.Files { defer file.Close() if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { continue } h, err := ioutil.ReadAll(file) if err != nil { return nil, err } t, err = t.New(name).Parse(string(h)) if err != nil { return nil, err } } return t, nil } ``` See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` 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(200, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) } func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStructPointer, "c": b.FieldC, }) } func GetDataD(c *gin.Context) { var b StructD c.Bind(&b) c.JSON(200, 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: ``` $ 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{} // This 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`. ```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.JSON); 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.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML`) } else { ... } } ``` * `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. * 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)). ### http2 server push http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. ```go package main import ( "html/template" "log" "github.com/gin-gonic/gin" ) var html = template.Must(template.New("https").Parse(` Https Test

Welcome, Ginner!

`)) func main() { r := gin.Default() r.Static("/assets", "./assets") r.SetHTMLTemplate(html) r.GET("/", func(c *gin.Context) { if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push if err := pusher.Push("/assets/app.js", nil); err != nil { log.Printf("Failed to push: %v", err) } } c.HTML(200, "https", gin.H{ "status": "success", }) }) // Listen and Server in https://127.0.0.1:8080 r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") } ``` ### Define format for the log of routes The default log of routes is: ``` [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 import ( "fmt" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/cookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") if err != nil { cookie = "NotSet" c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", http.SameSiteLaxMode, false, true) } fmt.Printf("Cookie value: %s \n", cookie) }) router.Run() } ``` ## Testing The `net/http/httptest` package is preferable way for HTTP testing. ```go package main func setupRouter() *gin.Engine { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) return r } func main() { r := setupRouter() r.Run(":8080") } ``` Test for code example above: ```go package main import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestPingRoute(t *testing.T) { router := setupRouter() w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/ping", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "pong", w.Body.String()) } ``` ## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. gin-1.6.3/auth.go000066400000000000000000000052101365354716400136020ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "encoding/base64" "net/http" "strconv" "github.com/gin-gonic/gin/internal/bytesconv" ) // AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" // Accounts defines a key/value for user/pass list of authorized logins. type Accounts map[string]string type authPair struct { value string user string } type authPairs []authPair func (a authPairs) searchCredential(authValue string) (string, bool) { if authValue == "" { return "", false } for _, pair := range a { if pair.value == authValue { return pair.user, true } } return "", false } // BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where // the key is the user name and the value is the password, as well as the name of the Realm. // If the realm is empty, "Authorization Required" will be used by default. // (see http://tools.ietf.org/html/rfc2617#section-1.2) func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { if realm == "" { realm = "Authorization Required" } realm = "Basic realm=" + strconv.Quote(realm) pairs := processAccounts(accounts) return func(c *Context) { // Search user in the slice of allowed credentials user, found := pairs.searchCredential(c.requestHeader("Authorization")) if !found { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) c.AbortWithStatus(http.StatusUnauthorized) return } // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using // c.MustGet(gin.AuthUserKey). c.Set(AuthUserKey, user) } } // BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where // the key is the user name and the value is the password. func BasicAuth(accounts Accounts) HandlerFunc { return BasicAuthForRealm(accounts, "") } func processAccounts(accounts Accounts) authPairs { assert1(len(accounts) > 0, "Empty list of authorized credentials") pairs := make(authPairs, 0, len(accounts)) for user, password := range accounts { assert1(user != "", "User can not be empty") value := authorizationHeader(user, password) pairs = append(pairs, authPair{ value: value, user: user, }) } return pairs } func authorizationHeader(user, password string) string { base := user + ":" + password return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } gin-1.6.3/auth_test.go000066400000000000000000000073261365354716400146530ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "encoding/base64" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestBasicAuth(t *testing.T) { pairs := processAccounts(Accounts{ "admin": "password", "foo": "bar", "bar": "foo", }) assert.Len(t, pairs, 3) assert.Contains(t, pairs, authPair{ user: "bar", value: "Basic YmFyOmZvbw==", }) assert.Contains(t, pairs, authPair{ user: "foo", value: "Basic Zm9vOmJhcg==", }) assert.Contains(t, pairs, authPair{ user: "admin", value: "Basic YWRtaW46cGFzc3dvcmQ=", }) } func TestBasicAuthFails(t *testing.T) { assert.Panics(t, func() { processAccounts(nil) }) assert.Panics(t, func() { processAccounts(Accounts{ "": "password", "foo": "bar", }) }) } func TestBasicAuthSearchCredential(t *testing.T) { pairs := processAccounts(Accounts{ "admin": "password", "foo": "bar", "bar": "foo", }) user, found := pairs.searchCredential(authorizationHeader("admin", "password")) assert.Equal(t, "admin", user) assert.True(t, found) user, found = pairs.searchCredential(authorizationHeader("foo", "bar")) assert.Equal(t, "foo", user) assert.True(t, found) user, found = pairs.searchCredential(authorizationHeader("bar", "foo")) assert.Equal(t, "bar", user) assert.True(t, found) user, found = pairs.searchCredential(authorizationHeader("admins", "password")) assert.Empty(t, user) assert.False(t, found) user, found = pairs.searchCredential(authorizationHeader("foo", "bar ")) assert.Empty(t, user) assert.False(t, found) user, found = pairs.searchCredential("") assert.Empty(t, user) assert.False(t, found) } func TestBasicAuthAuthorizationHeader(t *testing.T) { assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password")) } func TestBasicAuthSucceed(t *testing.T) { accounts := Accounts{"admin": "password"} router := New() router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/login", nil) req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "admin", w.Body.String()) } func TestBasicAuth401(t *testing.T) { called := false accounts := Accounts{"foo": "bar"} router := New() router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { called = true c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate")) } func TestBasicAuth401WithCustomRealm(t *testing.T) { called := false accounts := Accounts{"foo": "bar"} router := New() router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\"")) router.GET("/login", func(c *Context) { called = true c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate")) } gin-1.6.3/benchmarks_test.go000066400000000000000000000074321365354716400160250ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "html/template" "net/http" "os" "testing" ) func BenchmarkOneRoute(B *testing.B) { router := New() router.GET("/ping", func(c *Context) {}) runRequest(B, router, "GET", "/ping") } func BenchmarkRecoveryMiddleware(B *testing.B) { router := New() router.Use(Recovery()) router.GET("/", func(c *Context) {}) runRequest(B, router, "GET", "/") } func BenchmarkLoggerMiddleware(B *testing.B) { router := New() router.Use(LoggerWithWriter(newMockWriter())) router.GET("/", func(c *Context) {}) runRequest(B, router, "GET", "/") } func BenchmarkManyHandlers(B *testing.B) { router := New() router.Use(Recovery(), LoggerWithWriter(newMockWriter())) router.Use(func(c *Context) {}) router.Use(func(c *Context) {}) router.GET("/ping", func(c *Context) {}) runRequest(B, router, "GET", "/ping") } func Benchmark5Params(B *testing.B) { DefaultWriter = os.Stdout router := New() router.Use(func(c *Context) {}) router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {}) runRequest(B, router, "GET", "/param/path/to/parameter/john/12345") } func BenchmarkOneRouteJSON(B *testing.B) { router := New() data := struct { Status string `json:"status"` }{"ok"} router.GET("/json", func(c *Context) { c.JSON(http.StatusOK, data) }) runRequest(B, router, "GET", "/json") } func BenchmarkOneRouteHTML(B *testing.B) { router := New() t := template.Must(template.New("index").Parse(`

{{.}}

`)) router.SetHTMLTemplate(t) router.GET("/html", func(c *Context) { c.HTML(http.StatusOK, "index", "hola") }) runRequest(B, router, "GET", "/html") } func BenchmarkOneRouteSet(B *testing.B) { router := New() router.GET("/ping", func(c *Context) { c.Set("key", "value") }) runRequest(B, router, "GET", "/ping") } func BenchmarkOneRouteString(B *testing.B) { router := New() router.GET("/text", func(c *Context) { c.String(http.StatusOK, "this is a plain text") }) runRequest(B, router, "GET", "/text") } func BenchmarkManyRoutesFist(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) runRequest(B, router, "GET", "/ping") } func BenchmarkManyRoutesLast(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) runRequest(B, router, "OPTIONS", "/ping") } func Benchmark404(B *testing.B) { router := New() router.Any("/something", func(c *Context) {}) router.NoRoute(func(c *Context) {}) runRequest(B, router, "GET", "/ping") } func Benchmark404Many(B *testing.B) { router := New() router.GET("/", func(c *Context) {}) router.GET("/path/to/something", func(c *Context) {}) router.GET("/post/:id", func(c *Context) {}) router.GET("/view/:id", func(c *Context) {}) router.GET("/favicon.ico", func(c *Context) {}) router.GET("/robots.txt", func(c *Context) {}) router.GET("/delete/:id", func(c *Context) {}) router.GET("/user/:id/:mode", func(c *Context) {}) router.NoRoute(func(c *Context) {}) runRequest(B, router, "GET", "/viewfake") } type mockWriter struct { headers http.Header } func newMockWriter() *mockWriter { return &mockWriter{ http.Header{}, } } func (m *mockWriter) Header() (h http.Header) { return m.headers } func (m *mockWriter) Write(p []byte) (n int, err error) { return len(p), nil } func (m *mockWriter) WriteString(s string) (n int, err error) { return len(s), nil } func (m *mockWriter) WriteHeader(int) {} func runRequest(B *testing.B, r *Engine, method, path string) { // create fake request req, err := http.NewRequest(method, path, nil) if err != nil { panic(err) } w := newMockWriter() B.ReportAllocs() B.ResetTimer() for i := 0; i < B.N; i++ { r.ServeHTTP(w, req) } } gin-1.6.3/binding/000077500000000000000000000000001365354716400137265ustar00rootroot00000000000000gin-1.6.3/binding/binding.go000066400000000000000000000072561365354716400157010ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package binding import "net/http" // Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" MIMEXML = "application/xml" MIMEXML2 = "text/xml" MIMEPlain = "text/plain" MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" ) // Binding describes the interface which needs to be implemented for binding the // data present in the request such as JSON request body, query parameters or // the form POST. type Binding interface { Name() string Bind(*http.Request, interface{}) error } // BindingBody adds BindBody method to Binding. BindBody is similar with Bind, // but it reads the body from supplied bytes instead of req.Body. type BindingBody interface { Binding BindBody([]byte, interface{}) error } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // but it read the Params. type BindingUri interface { Name() string BindUri(map[string][]string, interface{}) error } // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the request. Gin provides a default implementation for this using // https://github.com/go-playground/validator/tree/v8.18.2. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // If the received type is not a struct, any validation should be skipped and nil must be returned. // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. ValidateStruct(interface{}) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. Engine() interface{} } // Validator is the default validator which implements the StructValidator // interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // under the hood. var Validator StructValidator = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. var ( JSON = jsonBinding{} XML = xmlBinding{} Form = formBinding{} Query = queryBinding{} FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} MsgPack = msgpackBinding{} YAML = yamlBinding{} Uri = uriBinding{} Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method // and the content type. func Default(method, contentType string) Binding { if method == http.MethodGet { return Form } switch contentType { case MIMEJSON: return JSON case MIMEXML, MIMEXML2: return XML case MIMEPROTOBUF: return ProtoBuf case MIMEMSGPACK, MIMEMSGPACK2: return MsgPack case MIMEYAML: return YAML case MIMEMultipartPOSTForm: return FormMultipart default: // case MIMEPOSTForm: return Form } } func validate(obj interface{}) error { if Validator == nil { return nil } return Validator.ValidateStruct(obj) } gin-1.6.3/binding/binding_msgpack_test.go000066400000000000000000000024641365354716400204410ustar00rootroot00000000000000// Copyright 2020 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package binding import ( "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) func TestBindingMsgPack(t *testing.T) { test := FooStruct{ Foo: "bar", } h := new(codec.MsgpackHandle) assert.NotNil(t, h) buf := bytes.NewBuffer([]byte{}) assert.NotNil(t, buf) err := codec.NewEncoder(buf, h).Encode(test) assert.NoError(t, err) data := buf.Bytes() testMsgPackBodyBinding(t, MsgPack, "msgpack", "/", "/", string(data), string(data[1:])) } func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEMSGPACK) err = MsgPack.Bind(req, &obj) assert.Error(t, err) } func TestBindingDefaultMsgPack(t *testing.T) { assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) } gin-1.6.3/binding/binding_nomsgpack.go000066400000000000000000000067471365354716400177470ustar00rootroot00000000000000// Copyright 2020 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build nomsgpack package binding import "net/http" // Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" MIMEXML = "application/xml" MIMEXML2 = "text/xml" MIMEPlain = "text/plain" MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEYAML = "application/x-yaml" ) // Binding describes the interface which needs to be implemented for binding the // data present in the request such as JSON request body, query parameters or // the form POST. type Binding interface { Name() string Bind(*http.Request, interface{}) error } // BindingBody adds BindBody method to Binding. BindBody is similar with Bind, // but it reads the body from supplied bytes instead of req.Body. type BindingBody interface { Binding BindBody([]byte, interface{}) error } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // but it read the Params. type BindingUri interface { Name() string BindUri(map[string][]string, interface{}) error } // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the request. Gin provides a default implementation for this using // https://github.com/go-playground/validator/tree/v8.18.2. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // If the received type is not a struct, any validation should be skipped and nil must be returned. // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. ValidateStruct(interface{}) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. Engine() interface{} } // Validator is the default validator which implements the StructValidator // interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // under the hood. var Validator StructValidator = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. var ( JSON = jsonBinding{} XML = xmlBinding{} Form = formBinding{} Query = queryBinding{} FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} YAML = yamlBinding{} Uri = uriBinding{} Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method // and the content type. func Default(method, contentType string) Binding { if method == "GET" { return Form } switch contentType { case MIMEJSON: return JSON case MIMEXML, MIMEXML2: return XML case MIMEPROTOBUF: return ProtoBuf case MIMEYAML: return YAML case MIMEMultipartPOSTForm: return FormMultipart default: // case MIMEPOSTForm: return Form } } func validate(obj interface{}) error { if Validator == nil { return nil } return Validator.ValidateStruct(obj) } gin-1.6.3/binding/binding_test.go000066400000000000000000000767361365354716400167510ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "encoding/json" "errors" "io" "io/ioutil" "mime/multipart" "net/http" "os" "strconv" "strings" "testing" "time" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" ) type appkey struct { Appkey string `json:"appkey" form:"appkey"` } type QueryTest struct { Page int `json:"page" form:"page"` Size int `json:"size" form:"size"` appkey } type FooStruct struct { Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` } type FooBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } type FooBarFileStruct struct { FooBarStruct File *multipart.FileHeader `form:"file" binding:"required"` } type FooBarFileFailStruct struct { FooBarStruct File *multipart.FileHeader `invalid_name:"file" binding:"required"` // for unexport test data *multipart.FileHeader `form:"data" binding:"required"` } type FooDefaultBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` } type FooStructUseNumber struct { Foo interface{} `json:"foo" binding:"required"` } type FooStructDisallowUnknownFields struct { Foo interface{} `json:"foo" binding:"required"` } type FooBarStructForTimeType struct { TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` } type FooStructForTimeTypeNotUnixFormat struct { CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` } type FooStructForTimeTypeNotFormat struct { TimeFoo time.Time `form:"time_foo"` } type FooStructForTimeTypeFailFormat struct { TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"` } type FooStructForTimeTypeFailLocation struct { TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"` } type FooStructForMapType struct { MapFoo map[string]interface{} `form:"map_foo"` } type FooStructForIgnoreFormTag struct { Foo *string `form:"-"` } type InvalidNameType struct { TestName string `invalid_name:"test_name"` } type InvalidNameMapType struct { TestName struct { MapFoo map[string]interface{} `form:"map_foo"` } } type FooStructForSliceType struct { SliceFoo []int `form:"slice_foo"` } type FooStructForStructType struct { StructFoo struct { Idx int `form:"idx"` } } type FooStructForStructPointerType struct { StructPointerFoo *struct { Name string `form:"name"` } } type FooStructForSliceMapType struct { // Unknown type: not support map SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` } type FooStructForBoolType struct { BoolFoo bool `form:"bool_foo"` } type FooStructForStringPtrType struct { PtrFoo *string `form:"ptr_foo"` PtrBar *string `form:"ptr_bar" binding:"required"` } type FooStructForMapPtrType struct { PtrBar *map[string]interface{} `form:"ptr_bar"` } func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("GET", "")) assert.Equal(t, Form, Default("GET", MIMEJSON)) assert.Equal(t, JSON, Default("POST", MIMEJSON)) assert.Equal(t, JSON, Default("PUT", MIMEJSON)) assert.Equal(t, XML, Default("POST", MIMEXML)) assert.Equal(t, XML, Default("PUT", MIMEXML2)) assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML)) } func TestBindingJSONNilBody(t *testing.T) { var obj FooStruct req, _ := http.NewRequest(http.MethodPost, "/", nil) err := JSON.Bind(req, &obj) assert.Error(t, err) } func TestBindingJSON(t *testing.T) { testBodyBinding(t, JSON, "json", "/", "/", `{"foo": "bar"}`, `{"bar": "foo"}`) } func TestBindingJSONUseNumber(t *testing.T) { testBodyBindingUseNumber(t, JSON, "json", "/", "/", `{"foo": 123}`, `{"bar": "foo"}`) } func TestBindingJSONUseNumber2(t *testing.T) { testBodyBindingUseNumber2(t, JSON, "json", "/", "/", `{"foo": 123}`, `{"bar": "foo"}`) } func TestBindingJSONDisallowUnknownFields(t *testing.T) { testBodyBindingDisallowUnknownFields(t, JSON, "/", "/", `{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`) } func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", "foo=bar&bar=foo", "bar2=foo") } func TestBindingForm2(t *testing.T) { testFormBinding(t, "GET", "/?foo=bar&bar=foo", "/?bar2=foo", "", "") } func TestBindingFormEmbeddedStruct(t *testing.T) { testFormBindingEmbeddedStruct(t, "POST", "/", "/", "page=1&size=2&appkey=test-appkey", "bar2=foo") } func TestBindingFormEmbeddedStruct2(t *testing.T) { testFormBindingEmbeddedStruct(t, "GET", "/?page=1&size=2&appkey=test-appkey", "/?bar2=foo", "", "") } func TestBindingFormDefaultValue(t *testing.T) { testFormBindingDefaultValue(t, "POST", "/", "/", "foo=bar", "bar2=foo") } func TestBindingFormDefaultValue2(t *testing.T) { testFormBindingDefaultValue(t, "GET", "/?foo=bar", "/?bar2=foo", "", "") } func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") testFormBindingForTimeNotUnixFormat(t, "POST", "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "POST", "/", "/", "time_foo=2017-11-15", "bar2=foo") testFormBindingForTimeFailFormat(t, "POST", "/", "/", "time_foo=2017-11-15", "bar2=foo") testFormBindingForTimeFailLocation(t, "POST", "/", "/", "time_foo=2017-11-15", "bar2=foo") } func TestBindingFormForTime2(t *testing.T) { testFormBindingForTime(t, "GET", "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", "", "") testFormBindingForTimeNotUnixFormat(t, "POST", "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "GET", "/?time_foo=2017-11-15", "/?bar2=foo", "", "") testFormBindingForTimeFailFormat(t, "GET", "/?time_foo=2017-11-15", "/?bar2=foo", "", "") testFormBindingForTimeFailLocation(t, "GET", "/?time_foo=2017-11-15", "/?bar2=foo", "", "") } func TestFormBindingIgnoreField(t *testing.T) { testFormBindingIgnoreField(t, "POST", "/", "/", "-=bar", "") } func TestBindingFormInvalidName(t *testing.T) { testFormBindingInvalidName(t, "POST", "/", "/", "test_name=bar", "bar2=foo") } func TestBindingFormInvalidName2(t *testing.T) { testFormBindingInvalidName2(t, "POST", "/", "/", "map_foo=bar", "bar2=foo") } func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "POST", "/", "/", "map_foo={\"bar\":123}", "map_foo=1", "Map") testFormBindingForType(t, "POST", "/", "/", "slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice") testFormBindingForType(t, "GET", "/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2", "", "", "Slice") testFormBindingForType(t, "POST", "/", "/", "slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap") testFormBindingForType(t, "GET", "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", "", "", "SliceMap") testFormBindingForType(t, "POST", "/", "/", "ptr_bar=test", "bar2=test", "Ptr") testFormBindingForType(t, "GET", "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") testFormBindingForType(t, "POST", "/", "/", "idx=123", "id1=1", "Struct") testFormBindingForType(t, "GET", "/?idx=123", "/?id1=1", "", "", "Struct") testFormBindingForType(t, "POST", "/", "/", "name=thinkerou", "name1=ou", "StructPointer") testFormBindingForType(t, "GET", "/?name=thinkerou", "/?name1=ou", "", "", "StructPointer") } func TestBindingQuery(t *testing.T) { testQueryBinding(t, "POST", "/?foo=bar&bar=foo", "/", "foo=unused", "bar2=foo") } func TestBindingQuery2(t *testing.T) { testQueryBinding(t, "GET", "/?foo=bar&bar=foo", "/?bar2=foo", "foo=unused", "") } func TestBindingQueryFail(t *testing.T) { testQueryBindingFail(t, "POST", "/?map_foo=", "/", "map_foo=unused", "bar2=foo") } func TestBindingQueryFail2(t *testing.T) { testQueryBindingFail(t, "GET", "/?map_foo=", "/?bar2=foo", "map_foo=unused", "") } func TestBindingQueryBoolFail(t *testing.T) { testQueryBindingBoolFail(t, "GET", "/?bool_foo=fasl", "/?bar2=foo", "bool_foo=unused", "") } func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", "/", "/", "bar", "foo") } func TestBindingXMLFail(t *testing.T) { testBodyBindingFail(t, XML, "xml", "/", "/", "bar", "foo") } func TestBindingYAML(t *testing.T) { testBodyBinding(t, YAML, "yaml", "/", "/", `foo: bar`, `bar: foo`) } func TestBindingYAMLFail(t *testing.T) { testBodyBindingFail(t, YAML, "yaml", "/", "/", `foo:\nbar`, `bar: foo`) } func createFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormFilesMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("foo", "bar")) assert.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") assert.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file", "form.go") assert.NoError(t, err1) _, err = io.Copy(fw, f) assert.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("foo", "bar")) assert.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") assert.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") assert.NoError(t, err1) _, err = io.Copy(fw, f) assert.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("foo", "bar")) assert.NoError(t, mw.WriteField("bar", "foo")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func createFormMultipartRequestForMap(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("map_foo", "3.14")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func TestBindingFormPost(t *testing.T) { req := createFormPostRequest(t) var obj FooBarStruct assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) } func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest(t) var obj FooDefaultBarStruct assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) } func TestBindingFormPostForMap(t *testing.T) { req := createFormPostRequestForMap(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) } func TestBindingFormPostForMapFail(t *testing.T) { req := createFormPostRequestForMapFail(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) assert.Error(t, err) } func TestBindingFormFilesMultipart(t *testing.T) { req := createFormFilesMultipartRequest(t) var obj FooBarFileStruct err := FormMultipart.Bind(req, &obj) assert.NoError(t, err) // file from os f, _ := os.Open("form.go") defer f.Close() fileActual, _ := ioutil.ReadAll(f) // file from multipart mf, _ := obj.File.Open() defer mf.Close() fileExpect, _ := ioutil.ReadAll(mf) assert.Equal(t, FormMultipart.Name(), "multipart/form-data") assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Bar, "foo") assert.Equal(t, fileExpect, fileActual) } func TestBindingFormFilesMultipartFail(t *testing.T) { req := createFormFilesMultipartRequestFail(t) var obj FooBarFileFailStruct err := FormMultipart.Bind(req, &obj) assert.Error(t, err) } func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct assert.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) } func TestBindingFormMultipartForMap(t *testing.T) { req := createFormMultipartRequestForMap(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string)) assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64)) } func TestBindingFormMultipartForMapFail(t *testing.T) { req := createFormMultipartRequestForMapFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.Error(t, err) } func TestBindingProtoBuf(t *testing.T) { test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) testProtoBodyBinding(t, ProtoBuf, "protobuf", "/", "/", string(data), string(data[1:])) } func TestBindingProtoBufFail(t *testing.T) { test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) testProtoBodyBindingFail(t, ProtoBuf, "protobuf", "/", "/", string(data), string(data[1:])) } func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) assert.Error(t, err) } func TestValidationDisabled(t *testing.T) { backup := Validator Validator = nil defer func() { Validator = backup }() var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) assert.NoError(t, err) } func TestRequiredSucceeds(t *testing.T) { type HogeStruct struct { Hoge *int `json:"hoge" binding:"required"` } var obj HogeStruct req := requestWithBody("POST", "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) assert.NoError(t, err) } func TestRequiredFails(t *testing.T) { type HogeStruct struct { Hoge *int `json:"foo" binding:"required"` } var obj HogeStruct req := requestWithBody("POST", "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) assert.Error(t, err) } func TestHeaderBinding(t *testing.T) { h := Header assert.Equal(t, "header", h.Name()) type tHeader struct { Limit int `header:"limit"` } var theader tHeader req := requestWithBody("GET", "/", "") req.Header.Add("limit", "1000") assert.NoError(t, h.Bind(req, &theader)) assert.Equal(t, 1000, theader.Limit) req = requestWithBody("GET", "/", "") req.Header.Add("fail", `{fail:fail}`) type failStruct struct { Fail map[string]interface{} `header:"fail"` } err := h.Bind(req, &failStruct{}) assert.Error(t, err) } func TestUriBinding(t *testing.T) { b := Uri assert.Equal(t, "uri", b.Name()) type Tag struct { Name string `uri:"name"` } var tag Tag m := make(map[string][]string) m["name"] = []string{"thinkerou"} assert.NoError(t, b.BindUri(m, &tag)) assert.Equal(t, "thinkerou", tag.Name) type NotSupportStruct struct { Name map[string]interface{} `uri:"name"` } var not NotSupportStruct assert.Error(t, b.BindUri(m, ¬)) assert.Equal(t, map[string]interface{}(nil), not.Name) } func TestUriInnerBinding(t *testing.T) { type Tag struct { Name string `uri:"name"` S struct { Age int `uri:"age"` } } expectedName := "mike" expectedAge := 25 m := map[string][]string{ "name": {expectedName}, "age": {strconv.Itoa(expectedAge)}, } var tag Tag assert.NoError(t, Uri.BindUri(m, &tag)) assert.Equal(t, tag.Name, expectedName) assert.Equal(t, tag.S.Age, expectedAge) } func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := QueryTest{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, 1, obj.Page) assert.Equal(t, 2, obj.Size) assert.Equal(t, "test-appkey", obj.Appkey) } func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooDefaultBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) obj = FooDefaultBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func TestFormBindingFail(t *testing.T) { b := Form assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) assert.Error(t, err) } func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() assert.NoError(t, err) err = Form.Bind(req, &obj) assert.Error(t, err) } func TestFormPostBindingFail(t *testing.T) { b := FormPost assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) assert.Error(t, err) } func TestFormMultipartBindingFail(t *testing.T) { b := FormMultipart assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) assert.Equal(t, "UTC", obj.TimeBar.Location().String()) assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano()) assert.Equal(t, int64(1562400033), obj.UnixTime.Unix()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeNotUnixFormat{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeNotUnixFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeNotFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeFailFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeFailLocation{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := FooStructForIgnoreFormTag{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Nil(t, obj.Foo) } func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := InvalidNameType{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) obj := InvalidNameMapType{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = InvalidNameMapType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { b := Form assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } switch typ { case "Slice": obj := FooStructForSliceType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Struct": obj := FooStructForStructType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, struct { Idx int "form:\"idx\"" }(struct { Idx int "form:\"idx\"" }{Idx: 123}), obj.StructFoo) case "StructPointer": obj := FooStructForStructPointerType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, struct { Name string "form:\"name\"" }(struct { Name string "form:\"name\"" }{Name: "thinkerou"}), *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) case "SliceMap": obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) assert.Error(t, err) case "Ptr": obj := FooStructForStringPtrType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Nil(t, obj.PtrFoo) assert.Equal(t, "test", *obj.PtrBar) obj = FooStructForStringPtrType{} obj.PtrBar = new(string) err = b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "test", *obj.PtrBar) objErr := FooStructForMapPtrType{} err = b.Bind(req, &objErr) assert.Error(t, err) obj = FooStructForStringPtrType{} req = requestWithBody(method, badPath, badBody) err = b.Bind(req, &obj) assert.Error(t, err) } } func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query assert.Equal(t, "query", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) } func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { b := Query assert.Equal(t, "query", b.Name()) obj := FooStructForMapType{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) } func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { b := Query assert.Equal(t, "query", b.Name()) obj := FooStructForBoolType{} req := requestWithBody(method, path, body) if method == "POST" { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) EnableDecoderUseNumber = true err := b.Bind(req, &obj) assert.NoError(t, err) // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() assert.NoError(t, e) assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) EnableDecoderUseNumber = false err := b.Bind(req, &obj) assert.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped assert.Equal(t, float64(123), obj.Foo) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) { EnableDecoderDisallowUnknownFields = true defer func() { EnableDecoderDisallowUnknownFields = false }() obj := FooStructDisallowUnknownFields{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStructDisallowUnknownFields{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) assert.Contains(t, err.Error(), "what") } func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.Error(t, err) assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) assert.Error(t, err) } type hook struct{} func (h hook) Read([]byte) (int, error) { return 0, errors.New("error") } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Body = ioutil.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.Error(t, err) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) assert.Error(t, err) } func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } gin-1.6.3/binding/default_validator.go000066400000000000000000000024401365354716400177460ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "reflect" "sync" "github.com/go-playground/validator/v10" ) type defaultValidator struct { once sync.Once validate *validator.Validate } var _ StructValidator = &defaultValidator{} // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj interface{}) error { value := reflect.ValueOf(obj) valueType := value.Kind() if valueType == reflect.Ptr { valueType = value.Elem().Kind() } if valueType == reflect.Struct { v.lazyinit() if err := v.validate.Struct(obj); err != nil { return err } } return nil } // Engine returns the underlying validator engine which powers the default // Validator instance. This is useful if you want to register custom validations // or struct level validations. See validator GoDoc for more info - // https://godoc.org/gopkg.in/go-playground/validator.v8 func (v *defaultValidator) Engine() interface{} { v.lazyinit() return v.validate } func (v *defaultValidator) lazyinit() { v.once.Do(func() { v.validate = validator.New() v.validate.SetTagName("binding") }) } gin-1.6.3/binding/form.go000066400000000000000000000025201365354716400152170ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "net/http" ) const defaultMemory = 32 << 20 type formBinding struct{} type formPostBinding struct{} type formMultipartBinding struct{} func (formBinding) Name() string { return "form" } func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } if err := req.ParseMultipartForm(defaultMemory); err != nil { if err != http.ErrNotMultipart { return err } } if err := mapForm(obj, req.Form); err != nil { return err } return validate(obj) } func (formPostBinding) Name() string { return "form-urlencoded" } func (formPostBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } if err := mapForm(obj, req.PostForm); err != nil { return err } return validate(obj) } func (formMultipartBinding) Name() string { return "multipart/form-data" } func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil { return err } return validate(obj) } gin-1.6.3/binding/form_mapping.go000066400000000000000000000176721365354716400167500ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "errors" "fmt" "reflect" "strconv" "strings" "time" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" ) var errUnknownType = errors.New("unknown type") func mapUri(ptr interface{}, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") } func mapForm(ptr interface{}, form map[string][]string) error { return mapFormByTag(ptr, form, "form") } var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { return mappingByPtr(ptr, formSource(form), tag) } // setter tries to set value on a walking by fields of a struct type setter interface { TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) } type formSource map[string][]string var _ setter = formSource(nil) // TrySet tries to set a value by request's form source (like map[string][]string) func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { return setByForm(value, field, form, tagValue, opt) } func mappingByPtr(ptr interface{}, setter setter, tag string) error { _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { if field.Tag.Get(tag) == "-" { // just ignoring this field return false, nil } var vKind = value.Kind() if vKind == reflect.Ptr { var isNew bool vPtr := value if value.IsNil() { isNew = true vPtr = reflect.New(value.Type().Elem()) } isSetted, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } if isNew && isSetted { value.Set(vPtr) } return isSetted, nil } if vKind != reflect.Struct || !field.Anonymous { ok, err := tryToSetValue(value, field, setter, tag) if err != nil { return false, err } if ok { return true, nil } } if vKind == reflect.Struct { tValue := value.Type() var isSetted bool for i := 0; i < value.NumField(); i++ { sf := tValue.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) if err != nil { return false, err } isSetted = isSetted || ok } return isSetted, nil } return false, nil } type setOptions struct { isDefaultExists bool defaultValue string } func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { var tagValue string var setOpt setOptions tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") if tagValue == "" { // default value is FieldName tagValue = field.Name } if tagValue == "" { // when field is "emptyField" variable return false, nil } var opt string for len(opts) > 0 { opt, opts = head(opts, ",") if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v } } return setter.TrySet(value, field, tagValue, setOpt) } func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { return false, nil } switch value.Kind() { case reflect.Slice: if !ok { vs = []string{opt.defaultValue} } return true, setSlice(vs, value, field) case reflect.Array: if !ok { vs = []string{opt.defaultValue} } if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } return true, setArray(vs, value, field) default: var val string if !ok { val = opt.defaultValue } if len(vs) > 0 { val = vs[0] } return true, setWithProperType(val, value, field) } } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { switch value.Kind() { case reflect.Int: return setIntField(val, 0, value) case reflect.Int8: return setIntField(val, 8, value) case reflect.Int16: return setIntField(val, 16, value) case reflect.Int32: return setIntField(val, 32, value) case reflect.Int64: switch value.Interface().(type) { case time.Duration: return setTimeDuration(val, value, field) } return setIntField(val, 64, value) case reflect.Uint: return setUintField(val, 0, value) case reflect.Uint8: return setUintField(val, 8, value) case reflect.Uint16: return setUintField(val, 16, value) case reflect.Uint32: return setUintField(val, 32, value) case reflect.Uint64: return setUintField(val, 64, value) case reflect.Bool: return setBoolField(val, value) case reflect.Float32: return setFloatField(val, 32, value) case reflect.Float64: return setFloatField(val, 64, value) case reflect.String: value.SetString(val) case reflect.Struct: switch value.Interface().(type) { case time.Time: return setTimeField(val, field, value) } return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) default: return errUnknownType } return nil } func setIntField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0" } intVal, err := strconv.ParseInt(val, 10, bitSize) if err == nil { field.SetInt(intVal) } return err } func setUintField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0" } uintVal, err := strconv.ParseUint(val, 10, bitSize) if err == nil { field.SetUint(uintVal) } return err } func setBoolField(val string, field reflect.Value) error { if val == "" { val = "false" } boolVal, err := strconv.ParseBool(val) if err == nil { field.SetBool(boolVal) } return err } func setFloatField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0.0" } floatVal, err := strconv.ParseFloat(val, bitSize) if err == nil { field.SetFloat(floatVal) } return err } func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { timeFormat := structField.Tag.Get("time_format") if timeFormat == "" { timeFormat = time.RFC3339 } switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixnano": tv, err := strconv.ParseInt(val, 10, 0) if err != nil { return err } d := time.Duration(1) if tf == "unixnano" { d = time.Second } t := time.Unix(tv/int64(d), tv%int64(d)) value.Set(reflect.ValueOf(t)) return nil } if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil } l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC } if locTag := structField.Tag.Get("time_location"); locTag != "" { loc, err := time.LoadLocation(locTag) if err != nil { return err } l = loc } t, err := time.ParseInLocation(timeFormat, val, l) if err != nil { return err } value.Set(reflect.ValueOf(t)) return nil } func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { err := setWithProperType(s, value.Index(i), field) if err != nil { return err } } return nil } func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) err := setArray(vals, slice, field) if err != nil { return err } value.Set(slice) return nil } func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { d, err := time.ParseDuration(val) if err != nil { return err } value.Set(reflect.ValueOf(d)) return nil } func head(str, sep string) (head string, tail string) { idx := strings.Index(str, sep) if idx < 0 { return str, "" } return str[:idx], str[idx+len(sep):] } gin-1.6.3/binding/form_mapping_benchmark_test.go000066400000000000000000000027251365354716400220120ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "testing" "time" "github.com/stretchr/testify/assert" ) var form = map[string][]string{ "name": {"mike"}, "friends": {"anna", "nicole"}, "id_number": {"12345678"}, "id_date": {"2018-01-20"}, } type structFull struct { Name string `form:"name"` Age int `form:"age,default=25"` Friends []string `form:"friends"` ID *struct { Number string `form:"id_number"` DateOfIssue time.Time `form:"id_date" time_format:"2006-01-02" time_utc:"true"` } Nationality *string `form:"nationality"` } func BenchmarkMapFormFull(b *testing.B) { var s structFull for i := 0; i < b.N; i++ { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") } } b.StopTimer() t := b assert.Equal(t, "mike", s.Name) assert.Equal(t, 25, s.Age) assert.Equal(t, []string{"anna", "nicole"}, s.Friends) assert.Equal(t, "12345678", s.ID.Number) assert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue) assert.Nil(t, s.Nationality) } type structName struct { Name string `form:"name"` } func BenchmarkMapFormName(b *testing.B) { var s structName for i := 0; i < b.N; i++ { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") } } b.StopTimer() t := b assert.Equal(t, "mike", s.Name) } gin-1.6.3/binding/form_mapping_test.go000066400000000000000000000157031365354716400200000ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "reflect" "testing" "time" "github.com/stretchr/testify/assert" ) func TestMappingBaseTypes(t *testing.T) { intPtr := func(i int) *int { return &i } for _, tt := range []struct { name string value interface{} form string expect interface{} }{ {"base type", struct{ F int }{}, "9", int(9)}, {"base type", struct{ F int8 }{}, "9", int8(9)}, {"base type", struct{ F int16 }{}, "9", int16(9)}, {"base type", struct{ F int32 }{}, "9", int32(9)}, {"base type", struct{ F int64 }{}, "9", int64(9)}, {"base type", struct{ F uint }{}, "9", uint(9)}, {"base type", struct{ F uint8 }{}, "9", uint8(9)}, {"base type", struct{ F uint16 }{}, "9", uint16(9)}, {"base type", struct{ F uint32 }{}, "9", uint32(9)}, {"base type", struct{ F uint64 }{}, "9", uint64(9)}, {"base type", struct{ F bool }{}, "True", true}, {"base type", struct{ F float32 }{}, "9.1", float32(9.1)}, {"base type", struct{ F float64 }{}, "9.1", float64(9.1)}, {"base type", struct{ F string }{}, "test", string("test")}, {"base type", struct{ F *int }{}, "9", intPtr(9)}, // zero values {"zero value", struct{ F int }{}, "", int(0)}, {"zero value", struct{ F uint }{}, "", uint(0)}, {"zero value", struct{ F bool }{}, "", false}, {"zero value", struct{ F float32 }{}, "", float32(0)}, } { tp := reflect.TypeOf(tt.value) testName := tt.name + ":" + tp.Field(0).Type.String() val := reflect.New(reflect.TypeOf(tt.value)) val.Elem().Set(reflect.ValueOf(tt.value)) field := val.Elem().Type().Field(0) _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") assert.NoError(t, err, testName) actual := val.Elem().Field(0).Interface() assert.Equal(t, tt.expect, actual, testName) } } func TestMappingDefault(t *testing.T) { var s struct { Int int `form:",default=9"` Slice []int `form:",default=9"` Array [1]int `form:",default=9"` } err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) assert.Equal(t, 9, s.Int) assert.Equal(t, []int{9}, s.Slice) assert.Equal(t, [1]int{9}, s.Array) } func TestMappingSkipField(t *testing.T) { var s struct { A int } err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) assert.Equal(t, 0, s.A) } func TestMappingIgnoreField(t *testing.T) { var s struct { A int `form:"A"` B int `form:"-"` } err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") assert.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.B) } func TestMappingUnexportedField(t *testing.T) { var s struct { A int `form:"a"` b int `form:"b"` } err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") assert.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.b) } func TestMappingPrivateField(t *testing.T) { var s struct { f int `form:"field"` } err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") assert.NoError(t, err) assert.Equal(t, int(0), s.f) } func TestMappingUnknownFieldType(t *testing.T) { var s struct { U uintptr } err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") assert.Error(t, err) assert.Equal(t, errUnknownType, err) } func TestMappingURI(t *testing.T) { var s struct { F int `uri:"field"` } err := mapUri(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) assert.Equal(t, int(6), s.F) } func TestMappingForm(t *testing.T) { var s struct { F int `form:"field"` } err := mapForm(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) assert.Equal(t, int(6), s.F) } func TestMappingTime(t *testing.T) { var s struct { Time time.Time LocalTime time.Time `time_format:"2006-01-02"` ZeroValue time.Time CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` } var err error time.Local, err = time.LoadLocation("Europe/Berlin") assert.NoError(t, err) err = mapForm(&s, map[string][]string{ "Time": {"2019-01-20T16:02:58Z"}, "LocalTime": {"2019-01-20"}, "ZeroValue": {}, "CSTTime": {"2019-01-20"}, "UTCTime": {"2019-01-20"}, }) assert.NoError(t, err) assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) // wrong location var wrongLoc struct { Time time.Time `time_location:"wrong"` } err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) assert.Error(t, err) // wrong time value var wrongTime struct { Time time.Time } err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) assert.Error(t, err) } func TestMapiingTimeDuration(t *testing.T) { var s struct { D time.Duration } // ok err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") assert.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") assert.Error(t, err) } func TestMappingSlice(t *testing.T) { var s struct { Slice []int `form:"slice,default=9"` } // default value err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) assert.Equal(t, []int{9}, s.Slice) // ok err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") assert.NoError(t, err) assert.Equal(t, []int{3, 4}, s.Slice) // error err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") assert.Error(t, err) } func TestMappingArray(t *testing.T) { var s struct { Array [2]int `form:"array,default=9"` } // wrong default err := mappingByPtr(&s, formSource{}, "form") assert.Error(t, err) // ok err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") assert.NoError(t, err) assert.Equal(t, [2]int{3, 4}, s.Array) // error - not enough vals err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") assert.Error(t, err) // error - wrong value err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") assert.Error(t, err) } func TestMappingStructField(t *testing.T) { var s struct { J struct { I int } } err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") assert.NoError(t, err) assert.Equal(t, 9, s.J.I) } func TestMappingMapField(t *testing.T) { var s struct { M map[string]int } err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") assert.NoError(t, err) assert.Equal(t, map[string]int{"one": 1}, s.M) } func TestMappingIgnoredCircularRef(t *testing.T) { type S struct { S *S `form:"-"` } var s S err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) } gin-1.6.3/binding/header.go000066400000000000000000000013431365354716400155060ustar00rootroot00000000000000package binding import ( "net/http" "net/textproto" "reflect" ) type headerBinding struct{} func (headerBinding) Name() string { return "header" } func (headerBinding) Bind(req *http.Request, obj interface{}) error { if err := mapHeader(obj, req.Header); err != nil { return err } return validate(obj) } func mapHeader(ptr interface{}, h map[string][]string) error { return mappingByPtr(ptr, headerSource(h), "header") } type headerSource map[string][]string var _ setter = headerSource(nil) func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) } gin-1.6.3/binding/json.go000066400000000000000000000030441365354716400152270ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "fmt" "io" "net/http" "github.com/gin-gonic/gin/internal/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an // interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method // on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to // return an error when the destination is a struct and the input contains object // keys which do not match any non-ignored, exported fields in the destination. var EnableDecoderDisallowUnknownFields = false type jsonBinding struct{} func (jsonBinding) Name() string { return "json" } func (jsonBinding) Bind(req *http.Request, obj interface{}) error { if req == nil || req.Body == nil { return fmt.Errorf("invalid request") } return decodeJSON(req.Body, obj) } func (jsonBinding) BindBody(body []byte, obj interface{}) error { return decodeJSON(bytes.NewReader(body), obj) } func decodeJSON(r io.Reader, obj interface{}) error { decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } if EnableDecoderDisallowUnknownFields { decoder.DisallowUnknownFields() } if err := decoder.Decode(obj); err != nil { return err } return validate(obj) } gin-1.6.3/binding/json_test.go000066400000000000000000000007461365354716400162740ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestJSONBindingBindBody(t *testing.T) { var s struct { Foo string `json:"foo"` } err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO"}`), &s) require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } gin-1.6.3/binding/msgpack.go000066400000000000000000000014301365354716400157000ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package binding import ( "bytes" "io" "net/http" "github.com/ugorji/go/codec" ) type msgpackBinding struct{} func (msgpackBinding) Name() string { return "msgpack" } func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { return decodeMsgPack(req.Body, obj) } func (msgpackBinding) BindBody(body []byte, obj interface{}) error { return decodeMsgPack(bytes.NewReader(body), obj) } func decodeMsgPack(r io.Reader, obj interface{}) error { cdc := new(codec.MsgpackHandle) if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { return err } return validate(obj) } gin-1.6.3/binding/msgpack_test.go000066400000000000000000000014301365354716400167370ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package binding import ( "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" ) func TestMsgpackBindingBindBody(t *testing.T) { type teststruct struct { Foo string `msgpack:"foo"` } var s teststruct err := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{"FOO"}), &s) require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } func msgpackBody(t *testing.T, obj interface{}) []byte { var bs bytes.Buffer h := &codec.MsgpackHandle{} err := codec.NewEncoder(&bs, h).Encode(obj) require.NoError(t, err) return bs.Bytes() } gin-1.6.3/binding/multipart_form_mapping.go000066400000000000000000000037671365354716400210510ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "errors" "mime/multipart" "net/http" "reflect" ) type multipartRequest http.Request var _ setter = (*multipartRequest)(nil) // TrySet tries to set a value by the multipart request with the binding a form file func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { if files := r.MultipartForm.File[key]; len(files) != 0 { return setByMultipartFormFile(value, field, files) } return setByForm(value, field, r.MultipartForm.Value, key, opt) } func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { switch value.Kind() { case reflect.Ptr: switch value.Interface().(type) { case *multipart.FileHeader: value.Set(reflect.ValueOf(files[0])) return true, nil } case reflect.Struct: switch value.Interface().(type) { case multipart.FileHeader: value.Set(reflect.ValueOf(*files[0])) return true, nil } case reflect.Slice: slice := reflect.MakeSlice(value.Type(), len(files), len(files)) isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) if err != nil || !isSetted { return isSetted, err } value.Set(slice) return true, nil case reflect.Array: return setArrayOfMultipartFormFiles(value, field, files) } return false, errors.New("unsupported field type for multipart.FileHeader") } func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { if value.Len() != len(files) { return false, errors.New("unsupported len of array for []*multipart.FileHeader") } for i := range files { setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) if err != nil || !setted { return setted, err } } return true, nil } gin-1.6.3/binding/multipart_form_mapping_test.go000066400000000000000000000073221365354716400220770ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "io/ioutil" "mime/multipart" "net/http" "testing" "github.com/stretchr/testify/assert" ) func TestFormMultipartBindingBindOneFile(t *testing.T) { var s struct { FileValue multipart.FileHeader `form:"file"` FilePtr *multipart.FileHeader `form:"file"` SliceValues []multipart.FileHeader `form:"file"` SlicePtrs []*multipart.FileHeader `form:"file"` ArrayValues [1]multipart.FileHeader `form:"file"` ArrayPtrs [1]*multipart.FileHeader `form:"file"` } file := testFile{"file", "file1", []byte("hello")} req := createRequestMultipartFiles(t, file) err := FormMultipart.Bind(req, &s) assert.NoError(t, err) assertMultipartFileHeader(t, &s.FileValue, file) assertMultipartFileHeader(t, s.FilePtr, file) assert.Len(t, s.SliceValues, 1) assertMultipartFileHeader(t, &s.SliceValues[0], file) assert.Len(t, s.SlicePtrs, 1) assertMultipartFileHeader(t, s.SlicePtrs[0], file) assertMultipartFileHeader(t, &s.ArrayValues[0], file) assertMultipartFileHeader(t, s.ArrayPtrs[0], file) } func TestFormMultipartBindingBindTwoFiles(t *testing.T) { var s struct { SliceValues []multipart.FileHeader `form:"file"` SlicePtrs []*multipart.FileHeader `form:"file"` ArrayValues [2]multipart.FileHeader `form:"file"` ArrayPtrs [2]*multipart.FileHeader `form:"file"` } files := []testFile{ {"file", "file1", []byte("hello")}, {"file", "file2", []byte("world")}, } req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, &s) assert.NoError(t, err) assert.Len(t, s.SliceValues, len(files)) assert.Len(t, s.SlicePtrs, len(files)) assert.Len(t, s.ArrayValues, len(files)) assert.Len(t, s.ArrayPtrs, len(files)) for i, file := range files { assertMultipartFileHeader(t, &s.SliceValues[i], file) assertMultipartFileHeader(t, s.SlicePtrs[i], file) assertMultipartFileHeader(t, &s.ArrayValues[i], file) assertMultipartFileHeader(t, s.ArrayPtrs[i], file) } } func TestFormMultipartBindingBindError(t *testing.T) { files := []testFile{ {"file", "file1", []byte("hello")}, {"file", "file2", []byte("world")}, } for _, tt := range []struct { name string s interface{} }{ {"wrong type", &struct { Files int `form:"file"` }{}}, {"wrong array size", &struct { Files [1]*multipart.FileHeader `form:"file"` }{}}, {"wrong slice type", &struct { Files []int `form:"file"` }{}}, } { req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, tt.s) assert.Error(t, err) } } type testFile struct { Fieldname string Filename string Content []byte } func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request { var body bytes.Buffer mw := multipart.NewWriter(&body) for _, file := range files { fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) assert.NoError(t, err) n, err := fw.Write(file.Content) assert.NoError(t, err) assert.Equal(t, len(file.Content), n) } err := mw.Close() assert.NoError(t, err) req, err := http.NewRequest("POST", "/", &body) assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) return req } func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { assert.Equal(t, file.Filename, fh.Filename) // assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 fl, err := fh.Open() assert.NoError(t, err) body, err := ioutil.ReadAll(fl) assert.NoError(t, err) assert.Equal(t, string(file.Content), string(body)) err = fl.Close() assert.NoError(t, err) } gin-1.6.3/binding/protobuf.go000066400000000000000000000015401365354716400161150ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "io/ioutil" "net/http" "github.com/golang/protobuf/proto" ) type protobufBinding struct{} func (protobufBinding) Name() string { return "protobuf" } func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { buf, err := ioutil.ReadAll(req.Body) if err != nil { return err } return b.BindBody(buf, obj) } func (protobufBinding) BindBody(body []byte, obj interface{}) error { if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { return err } // Here it's same to return validate(obj), but util now we can't add // `binding:""` to the struct which automatically generate by gen-proto return nil // return validate(obj) } gin-1.6.3/binding/query.go000066400000000000000000000007251365354716400154260ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import "net/http" type queryBinding struct{} func (queryBinding) Name() string { return "query" } func (queryBinding) Bind(req *http.Request, obj interface{}) error { values := req.URL.Query() if err := mapForm(obj, values); err != nil { return err } return validate(obj) } gin-1.6.3/binding/uri.go000066400000000000000000000006301365354716400150530ustar00rootroot00000000000000// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding type uriBinding struct{} func (uriBinding) Name() string { return "uri" } func (uriBinding) BindUri(m map[string][]string, obj interface{}) error { if err := mapUri(obj, m); err != nil { return err } return validate(obj) } gin-1.6.3/binding/validate_test.go000066400000000000000000000124531365354716400171120ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "testing" "time" "github.com/go-playground/validator/v10" "github.com/stretchr/testify/assert" ) type testInterface interface { String() string } type substructNoValidation struct { IString string IInt int } type mapNoValidationSub map[string]substructNoValidation type structNoValidationValues struct { substructNoValidation Boolean bool Uinteger uint Integer int Integer8 int8 Integer16 int16 Integer32 int32 Integer64 int64 Uinteger8 uint8 Uinteger16 uint16 Uinteger32 uint32 Uinteger64 uint64 Float32 float32 Float64 float64 String string Date time.Time Struct substructNoValidation InlinedStruct struct { String []string Integer int } IntSlice []int IntPointerSlice []*int StructPointerSlice []*substructNoValidation StructSlice []substructNoValidation InterfaceSlice []testInterface UniversalInterface interface{} CustomInterface testInterface FloatMap map[string]float32 StructMap mapNoValidationSub } func createNoValidationValues() structNoValidationValues { integer := 1 s := structNoValidationValues{ Boolean: true, Uinteger: 1 << 29, Integer: -10000, Integer8: 120, Integer16: -20000, Integer32: 1 << 29, Integer64: 1 << 61, Uinteger8: 250, Uinteger16: 50000, Uinteger32: 1 << 31, Uinteger64: 1 << 62, Float32: 123.456, Float64: 123.456789, String: "text", Date: time.Time{}, CustomInterface: &bytes.Buffer{}, Struct: substructNoValidation{}, IntSlice: []int{-3, -2, 1, 0, 1, 2, 3}, IntPointerSlice: []*int{&integer}, StructSlice: []substructNoValidation{}, UniversalInterface: 1.2, FloatMap: map[string]float32{ "foo": 1.23, "bar": 232.323, }, StructMap: mapNoValidationSub{ "foo": substructNoValidation{}, "bar": substructNoValidation{}, }, // StructPointerSlice []noValidationSub // InterfaceSlice []testInterface } s.InlinedStruct.Integer = 1000 s.InlinedStruct.String = []string{"first", "second"} s.IString = "substring" s.IInt = 987654 return s } func TestValidateNoValidationValues(t *testing.T) { origin := createNoValidationValues() test := createNoValidationValues() empty := structNoValidationValues{} assert.Nil(t, validate(test)) assert.Nil(t, validate(&test)) assert.Nil(t, validate(empty)) assert.Nil(t, validate(&empty)) assert.Equal(t, origin, test) } type structNoValidationPointer struct { substructNoValidation Boolean bool Uinteger *uint Integer *int Integer8 *int8 Integer16 *int16 Integer32 *int32 Integer64 *int64 Uinteger8 *uint8 Uinteger16 *uint16 Uinteger32 *uint32 Uinteger64 *uint64 Float32 *float32 Float64 *float64 String *string Date *time.Time Struct *substructNoValidation IntSlice *[]int IntPointerSlice *[]*int StructPointerSlice *[]*substructNoValidation StructSlice *[]substructNoValidation InterfaceSlice *[]testInterface FloatMap *map[string]float32 StructMap *mapNoValidationSub } func TestValidateNoValidationPointers(t *testing.T) { //origin := createNoValidation_values() //test := createNoValidation_values() empty := structNoValidationPointer{} //assert.Nil(t, validate(test)) //assert.Nil(t, validate(&test)) assert.Nil(t, validate(empty)) assert.Nil(t, validate(&empty)) //assert.Equal(t, origin, test) } type Object map[string]interface{} func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} assert.NoError(t, validate(obj)) assert.NoError(t, validate(&obj)) assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} assert.NoError(t, validate(obj2)) assert.NoError(t, validate(&obj2)) nu := 10 assert.NoError(t, validate(nu)) assert.NoError(t, validate(&nu)) assert.Equal(t, 10, nu) str := "value" assert.NoError(t, validate(str)) assert.NoError(t, validate(&str)) assert.Equal(t, "value", str) } // structCustomValidation is a helper struct we use to check that // custom validation can be registered on it. // The `notone` binding directive is for custom validation and registered later. type structCustomValidation struct { Integer int `binding:"notone"` } func notOne(f1 validator.FieldLevel) bool { if val, ok := f1.Field().Interface().(int); ok { return val != 1 } return false } func TestValidatorEngine(t *testing.T) { // This validates that the function `notOne` matches // the expected function signature by `defaultValidator` // and by extension the validator library. engine, ok := Validator.Engine().(*validator.Validate) assert.True(t, ok) err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error assert.Nil(t, err) // Create an instance which will fail validation withOne := structCustomValidation{Integer: 1} errs := validate(withOne) // Check that we got back non-nil errs assert.NotNil(t, errs) // Check that the error matches expectation assert.Error(t, errs, "", "", "notone") } gin-1.6.3/binding/xml.go000066400000000000000000000012751365354716400150620ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "encoding/xml" "io" "net/http" ) type xmlBinding struct{} func (xmlBinding) Name() string { return "xml" } func (xmlBinding) Bind(req *http.Request, obj interface{}) error { return decodeXML(req.Body, obj) } func (xmlBinding) BindBody(body []byte, obj interface{}) error { return decodeXML(bytes.NewReader(body), obj) } func decodeXML(r io.Reader, obj interface{}) error { decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err } return validate(obj) } gin-1.6.3/binding/xml_test.go000066400000000000000000000010601365354716400161110ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestXMLBindingBindBody(t *testing.T) { var s struct { Foo string `xml:"foo"` } xmlBody := ` FOO ` err := xmlBinding{}.BindBody([]byte(xmlBody), &s) require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } gin-1.6.3/binding/yaml.go000066400000000000000000000013041365354716400152150ustar00rootroot00000000000000// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "bytes" "io" "net/http" "gopkg.in/yaml.v2" ) type yamlBinding struct{} func (yamlBinding) Name() string { return "yaml" } func (yamlBinding) Bind(req *http.Request, obj interface{}) error { return decodeYAML(req.Body, obj) } func (yamlBinding) BindBody(body []byte, obj interface{}) error { return decodeYAML(bytes.NewReader(body), obj) } func decodeYAML(r io.Reader, obj interface{}) error { decoder := yaml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err } return validate(obj) } gin-1.6.3/binding/yaml_test.go000066400000000000000000000007401365354716400162570ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestYAMLBindingBindBody(t *testing.T) { var s struct { Foo string `yaml:"foo"` } err := yamlBinding{}.BindBody([]byte("foo: FOO"), &s) require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } gin-1.6.3/codecov.yml000066400000000000000000000001561365354716400144630ustar00rootroot00000000000000coverage: notify: gitter: default: url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165 gin-1.6.3/context.go000066400000000000000000001034001365354716400143250ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "errors" "fmt" "io" "io/ioutil" "math" "mime/multipart" "net" "net/http" "net/url" "os" "strings" "sync" "time" "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" ) // Content-Type MIME of the most common data formats. const ( MIMEJSON = binding.MIMEJSON MIMEHTML = binding.MIMEHTML MIMEXML = binding.MIMEXML MIMEXML2 = binding.MIMEXML2 MIMEPlain = binding.MIMEPlain MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEYAML = binding.MIMEYAML BodyBytesKey = "_gin-gonic/gin/bodybyteskey" ) const abortIndex int8 = math.MaxInt8 / 2 // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. type Context struct { writermem responseWriter Request *http.Request Writer ResponseWriter Params Params handlers HandlersChain index int8 fullPath string engine *Engine // This mutex protect Keys map mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() queryCache url.Values // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, // or PUT body parameters. formCache url.Values // SameSite allows a server to define a cookie attribute making it impossible for // the browser to send this cookie along with cross-site requests. sameSite http.SameSite } /************************************/ /********** CONTEXT CREATION ********/ /************************************/ func (c *Context) reset() { c.Writer = &c.writermem c.Params = c.Params[0:0] c.handlers = nil c.index = -1 c.fullPath = "" c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil c.queryCache = nil c.formCache = nil } // Copy returns a copy of the current context that can be safely used outside the request's scope. // This has to be used when the context has to be passed to a goroutine. func (c *Context) Copy() *Context { cp := Context{ writermem: c.writermem, Request: c.Request, Params: c.Params, engine: c.engine, } cp.writermem.ResponseWriter = nil cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil cp.Keys = map[string]interface{}{} for k, v := range c.Keys { cp.Keys[k] = v } paramCopy := make([]Param, len(cp.Params)) copy(paramCopy, cp.Params) cp.Params = paramCopy return &cp } // HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", // this function will return "main.handleGetUsers". func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } // HandlerNames returns a list of all registered handlers for this context in descending order, // following the semantics of HandlerName() func (c *Context) HandlerNames() []string { hn := make([]string, 0, len(c.handlers)) for _, val := range c.handlers { hn = append(hn, nameOfFunction(val)) } return hn } // Handler returns the main handler. func (c *Context) Handler() HandlerFunc { return c.handlers.Last() } // FullPath returns a matched route full path. For not found routes // returns an empty string. // router.GET("/user/:id", func(c *gin.Context) { // c.FullPath() == "/user/:id" // true // }) func (c *Context) FullPath() string { return c.fullPath } /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ // Next should be used only inside middleware. // It executes the pending handlers in the chain inside the calling handler. // See example in GitHub. func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } } // IsAborted returns true if the current context was aborted. func (c *Context) IsAborted() bool { return c.index >= abortIndex } // Abort prevents pending handlers from being called. Note that this will not stop the current handler. // Let's say you have an authorization middleware that validates that the current request is authorized. // If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers // for this request are not called. func (c *Context) Abort() { c.index = abortIndex } // AbortWithStatus calls `Abort()` and writes the headers with the specified status code. // For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401). func (c *Context) AbortWithStatus(code int) { c.Status(code) c.Writer.WriteHeaderNow() c.Abort() } // AbortWithStatusJSON calls `Abort()` and then `JSON` internally. // This method stops the chain, writes the status code and return a JSON body. // It also sets the Content-Type as "application/json". func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) { c.Abort() c.JSON(code, jsonObj) } // AbortWithError calls `AbortWithStatus()` and `Error()` internally. // This method stops the chain, writes the status code and pushes the specified error to `c.Errors`. // See Context.Error() for more details. func (c *Context) AbortWithError(code int, err error) *Error { c.AbortWithStatus(code) return c.Error(err) } /************************************/ /********* ERROR MANAGEMENT *********/ /************************************/ // Error attaches an error to the current context. The error is pushed to a list of errors. // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors and push them to a database together, // print a log, or append it in the HTTP response. // Error will panic if err is nil. func (c *Context) Error(err error) *Error { if err == nil { panic("err is nil") } parsedError, ok := err.(*Error) if !ok { parsedError = &Error{ Err: err, Type: ErrorTypePrivate, } } c.Errors = append(c.Errors, parsedError) return parsedError } /************************************/ /******** METADATA MANAGEMENT********/ /************************************/ // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value interface{}) { c.mu.Lock() if c.Keys == nil { c.Keys = make(map[string]interface{}) } c.Keys[key] = value c.mu.Unlock() } // Get returns the value for the given key, ie: (value, true). // If the value does not exists it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { c.mu.RLock() value, exists = c.Keys[key] c.mu.RUnlock() return } // MustGet returns the value for the given key if it exists, otherwise it panics. func (c *Context) MustGet(key string) interface{} { if value, exists := c.Get(key); exists { return value } panic("Key \"" + key + "\" does not exist") } // GetString returns the value associated with the key as a string. func (c *Context) GetString(key string) (s string) { if val, ok := c.Get(key); ok && val != nil { s, _ = val.(string) } return } // GetBool returns the value associated with the key as a boolean. func (c *Context) GetBool(key string) (b bool) { if val, ok := c.Get(key); ok && val != nil { b, _ = val.(bool) } return } // GetInt returns the value associated with the key as an integer. func (c *Context) GetInt(key string) (i int) { if val, ok := c.Get(key); ok && val != nil { i, _ = val.(int) } return } // GetInt64 returns the value associated with the key as an integer. func (c *Context) GetInt64(key string) (i64 int64) { if val, ok := c.Get(key); ok && val != nil { i64, _ = val.(int64) } return } // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { if val, ok := c.Get(key); ok && val != nil { f64, _ = val.(float64) } return } // GetTime returns the value associated with the key as time. func (c *Context) GetTime(key string) (t time.Time) { if val, ok := c.Get(key); ok && val != nil { t, _ = val.(time.Time) } return } // GetDuration returns the value associated with the key as a duration. func (c *Context) GetDuration(key string) (d time.Duration) { if val, ok := c.Get(key); ok && val != nil { d, _ = val.(time.Duration) } return } // GetStringSlice returns the value associated with the key as a slice of strings. func (c *Context) GetStringSlice(key string) (ss []string) { if val, ok := c.Get(key); ok && val != nil { ss, _ = val.([]string) } return } // GetStringMap returns the value associated with the key as a map of interfaces. func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { if val, ok := c.Get(key); ok && val != nil { sm, _ = val.(map[string]interface{}) } return } // GetStringMapString returns the value associated with the key as a map of strings. func (c *Context) GetStringMapString(key string) (sms map[string]string) { if val, ok := c.Get(key); ok && val != nil { sms, _ = val.(map[string]string) } return } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { if val, ok := c.Get(key); ok && val != nil { smss, _ = val.(map[string][]string) } return } /************************************/ /************ INPUT DATA ************/ /************************************/ // Param returns the value of the URL param. // It is a shortcut for c.Params.ByName(key) // router.GET("/user/:id", func(c *gin.Context) { // // a GET request to /user/john // id := c.Param("id") // id == "john" // }) func (c *Context) Param(key string) string { return c.Params.ByName(key) } // Query returns the keyed url query value if it exists, // otherwise it returns an empty string `("")`. // It is shortcut for `c.Request.URL.Query().Get(key)` // GET /path?id=1234&name=Manu&value= // c.Query("id") == "1234" // c.Query("name") == "Manu" // c.Query("value") == "" // c.Query("wtf") == "" func (c *Context) Query(key string) string { value, _ := c.GetQuery(key) return value } // DefaultQuery returns the keyed url query value if it exists, // otherwise it returns the specified defaultValue string. // See: Query() and GetQuery() for further information. // GET /?name=Manu&lastname= // c.DefaultQuery("name", "unknown") == "Manu" // c.DefaultQuery("id", "none") == "none" // c.DefaultQuery("lastname", "none") == "" func (c *Context) DefaultQuery(key, defaultValue string) string { if value, ok := c.GetQuery(key); ok { return value } return defaultValue } // GetQuery is like Query(), it returns the keyed url query value // if it exists `(value, true)` (even when the value is an empty string), // otherwise it returns `("", false)`. // It is shortcut for `c.Request.URL.Query().Get(key)` // GET /?name=Manu&lastname= // ("Manu", true) == c.GetQuery("name") // ("", false) == c.GetQuery("id") // ("", true) == c.GetQuery("lastname") func (c *Context) GetQuery(key string) (string, bool) { if values, ok := c.GetQueryArray(key); ok { return values[0], ok } return "", false } // QueryArray returns a slice of strings for a given query key. // The length of the slice depends on the number of params with the given key. func (c *Context) QueryArray(key string) []string { values, _ := c.GetQueryArray(key) return values } func (c *Context) getQueryCache() { if c.queryCache == nil { c.queryCache = c.Request.URL.Query() } } // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetQueryArray(key string) ([]string, bool) { c.getQueryCache() if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } return []string{}, false } // QueryMap returns a map for a given query key. func (c *Context) QueryMap(key string) map[string]string { dicts, _ := c.GetQueryMap(key) return dicts } // GetQueryMap returns a map for a given query key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { c.getQueryCache() return c.get(c.queryCache, key) } // PostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty string `("")`. func (c *Context) PostForm(key string) string { value, _ := c.GetPostForm(key) return value } // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns the specified defaultValue string. // See: PostForm() and GetPostForm() for further information. func (c *Context) DefaultPostForm(key, defaultValue string) string { if value, ok := c.GetPostForm(key); ok { return value } return defaultValue } // GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded // form or multipart form when it exists `(value, true)` (even when the value is an empty string), // otherwise it returns ("", false). // For example, during a PATCH request to update the user's email: // email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" // email= --> ("", true) := GetPostForm("email") // set email to "" // --> ("", false) := GetPostForm("email") // do nothing with email func (c *Context) GetPostForm(key string) (string, bool) { if values, ok := c.GetPostFormArray(key); ok { return values[0], ok } return "", false } // PostFormArray returns a slice of strings for a given form key. // The length of the slice depends on the number of params with the given key. func (c *Context) PostFormArray(key string) []string { values, _ := c.GetPostFormArray(key) return values } func (c *Context) getFormCache() { if c.formCache == nil { c.formCache = make(url.Values) req := c.Request if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { if err != http.ErrNotMultipart { debugPrint("error on parse multipart form array: %v", err) } } c.formCache = req.PostForm } } // GetPostFormArray returns a slice of strings for a given form key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { c.getFormCache() if values := c.formCache[key]; len(values) > 0 { return values, true } return []string{}, false } // PostFormMap returns a map for a given form key. func (c *Context) PostFormMap(key string) map[string]string { dicts, _ := c.GetPostFormMap(key) return dicts } // GetPostFormMap returns a map for a given form key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { c.getFormCache() return c.get(c.formCache, key) } // get is an internal method and returns a map which satisfy conditions. func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { dicts := make(map[string]string) exist := false for k, v := range m { if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { exist = true dicts[k[i+1:][:j]] = v[0] } } } return dicts, exist } // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { if c.Request.MultipartForm == nil { if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { return nil, err } } f, fh, err := c.Request.FormFile(name) if err != nil { return nil, err } f.Close() return fh, err } // MultipartForm is the parsed multipart form, including file uploads. func (c *Context) MultipartForm() (*multipart.Form, error) { err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory) return c.Request.MultipartForm, err } // SaveUploadedFile uploads the form file to specific dst. func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error { src, err := file.Open() if err != nil { return err } defer src.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, src) return err } // Bind checks the Content-Type to select a binding engine automatically, // Depending the "Content-Type" header different bindings are used: // "application/json" --> JSON binding // "application/xml" --> XML binding // otherwise --> returns an error. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.MustBindWith(obj, b) } // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). func (c *Context) BindXML(obj interface{}) error { return c.MustBindWith(obj, binding.XML) } // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) } // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). func (c *Context) BindHeader(obj interface{}) error { return c.MustBindWith(obj, binding.Header) } // BindUri binds the passed struct pointer using binding.Uri. // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { if err := c.ShouldBindUri(obj); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil } // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil } // ShouldBind checks the Content-Type to select a binding engine automatically, // Depending the "Content-Type" header different bindings are used: // "application/json" --> JSON binding // "application/xml" --> XML binding // otherwise --> returns an error // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. func (c *Context) ShouldBind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.ShouldBindWith(obj, b) } // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). func (c *Context) ShouldBindJSON(obj interface{}) error { return c.ShouldBindWith(obj, binding.JSON) } // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). func (c *Context) ShouldBindXML(obj interface{}) error { return c.ShouldBindWith(obj, binding.XML) } // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). func (c *Context) ShouldBindQuery(obj interface{}) error { return c.ShouldBindWith(obj, binding.Query) } // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). func (c *Context) ShouldBindYAML(obj interface{}) error { return c.ShouldBindWith(obj, binding.YAML) } // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). func (c *Context) ShouldBindHeader(obj interface{}) error { return c.ShouldBindWith(obj, binding.Header) } // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj interface{}) error { m := make(map[string][]string) for _, v := range c.Params { m[v.Key] = []string{v.Value} } return binding.Uri.BindUri(m, obj) } // ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) } // ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request // body into the context, and reuse when it is called again. // // NOTE: This method reads the body before binding. So you should use // ShouldBindWith for better performance if you need to call only once. func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) { var body []byte if cb, ok := c.Get(BodyBytesKey); ok { if cbb, ok := cb.([]byte); ok { body = cbb } } if body == nil { body, err = ioutil.ReadAll(c.Request.Body) if err != nil { return err } c.Set(BodyBytesKey, body) } return bb.BindBody(body, obj) } // ClientIP implements a best effort algorithm to return the real client IP, it parses // X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. // Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. func (c *Context) ClientIP() string { if c.engine.ForwardedByClientIP { clientIP := c.requestHeader("X-Forwarded-For") clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) if clientIP == "" { clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) } if clientIP != "" { return clientIP } } if c.engine.AppEngine { if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { return addr } } if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil { return ip } return "" } // ContentType returns the Content-Type header of the request. func (c *Context) ContentType() string { return filterFlags(c.requestHeader("Content-Type")) } // IsWebsocket returns true if the request headers indicate that a websocket // handshake is being initiated by the client. func (c *Context) IsWebsocket() bool { if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && strings.EqualFold(c.requestHeader("Upgrade"), "websocket") { return true } return false } func (c *Context) requestHeader(key string) string { return c.Request.Header.Get(key) } /************************************/ /******** RESPONSE RENDERING ********/ /************************************/ // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: return false case status == http.StatusNoContent: return false case status == http.StatusNotModified: return false } return true } // Status sets the HTTP response code. func (c *Context) Status(code int) { c.Writer.WriteHeader(code) } // Header is a intelligent shortcut for c.Writer.Header().Set(key, value). // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { if value == "" { c.Writer.Header().Del(key) return } c.Writer.Header().Set(key, value) } // GetHeader returns value from request headers. func (c *Context) GetHeader(key string) string { return c.requestHeader(key) } // GetRawData return stream data. func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } // SetSameSite with cookie func (c *Context) SetSameSite(samesite http.SameSite) { c.sameSite = samesite } // SetCookie adds a Set-Cookie header to the ResponseWriter's headers. // The provided cookie must have a valid Name. Invalid cookies may be // silently dropped. func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { if path == "" { path = "/" } http.SetCookie(c.Writer, &http.Cookie{ Name: name, Value: url.QueryEscape(value), MaxAge: maxAge, Path: path, Domain: domain, SameSite: c.sameSite, Secure: secure, HttpOnly: httpOnly, }) } // Cookie returns the named cookie provided in the request or // ErrNoCookie if not found. And return the named cookie is unescaped. // If multiple cookies match the given name, only one cookie will // be returned. func (c *Context) Cookie(name string) (string, error) { cookie, err := c.Request.Cookie(name) if err != nil { return "", err } val, _ := url.QueryUnescape(cookie.Value) return val, nil } // Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) if !bodyAllowedForStatus(code) { r.WriteContentType(c.Writer) c.Writer.WriteHeaderNow() return } if err := r.Render(c.Writer); err != nil { panic(err) } } // HTML renders the HTTP template specified by its file name. // It also updates the HTTP code and sets the Content-Type as "text/html". // See http://golang.org/doc/articles/wiki/ func (c *Context) HTML(code int, name string, obj interface{}) { instance := c.engine.HTMLRender.Instance(name, obj) c.Render(code, instance) } // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. // It also sets the Content-Type as "application/json". // WARNING: we recommend to use this only for development purposes since printing pretty JSON is // more CPU and bandwidth consuming. Use Context.JSON() instead. func (c *Context) IndentedJSON(code int, obj interface{}) { c.Render(code, render.IndentedJSON{Data: obj}) } // SecureJSON serializes the given struct as Secure JSON into the response body. // Default prepends "while(1)," to response body if the given struct is array values. // It also sets the Content-Type as "application/json". func (c *Context) SecureJSON(code int, obj interface{}) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) } // JSONP serializes the given struct as JSON into the response body. // It add padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) return } c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) } // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. // It also sets the Content-Type as "application/json". func (c *Context) AsciiJSON(code int, obj interface{}) { c.Render(code, render.AsciiJSON{Data: obj}) } // PureJSON serializes the given struct as JSON into the response body. // PureJSON, unlike JSON, does not replace special html characters with their unicode entities. func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { c.Render(code, render.XML{Data: obj}) } // YAML serializes the given struct as YAML into the response body. func (c *Context) YAML(code int, obj interface{}) { c.Render(code, render.YAML{Data: obj}) } // ProtoBuf serializes the given struct as ProtoBuf into the response body. func (c *Context) ProtoBuf(code int, obj interface{}) { c.Render(code, render.ProtoBuf{Data: obj}) } // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { c.Render(code, render.String{Format: format, Data: values}) } // Redirect returns a HTTP redirect to the specific location. func (c *Context) Redirect(code int, location string) { c.Render(-1, render.Redirect{ Code: code, Location: location, Request: c.Request, }) } // Data writes some data into the body stream and updates the HTTP code. func (c *Context) Data(code int, contentType string, data []byte) { c.Render(code, render.Data{ ContentType: contentType, Data: data, }) } // DataFromReader writes the specified reader into the body stream and updates the HTTP code. func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { c.Render(code, render.Reader{ Headers: extraHeaders, ContentType: contentType, ContentLength: contentLength, Reader: reader, }) } // File writes the specified file into the body stream in a efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } // FileFromFS writes the specified file from http.FileSytem into the body stream in an efficient way. func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { defer func(old string) { c.Request.URL.Path = old }(c.Request.URL.Path) c.Request.URL.Path = filepath http.FileServer(fs).ServeHTTP(c.Writer, c.Request) } // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) http.ServeFile(c.Writer, c.Request, filepath) } // SSEvent writes a Server-Sent Event into the body stream. func (c *Context) SSEvent(name string, message interface{}) { c.Render(-1, sse.Event{ Event: name, Data: message, }) } // Stream sends a streaming response and returns a boolean // indicates "Is client disconnected in middle of stream" func (c *Context) Stream(step func(w io.Writer) bool) bool { w := c.Writer clientGone := w.CloseNotify() for { select { case <-clientGone: return true default: keepOpen := step(w) w.Flush() if !keepOpen { return false } } } } /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ // Negotiate contains all negotiations data. type Negotiate struct { Offered []string HTMLName string HTMLData interface{} JSONData interface{} XMLData interface{} YAMLData interface{} Data interface{} } // Negotiate calls different Render according acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { case binding.MIMEJSON: data := chooseData(config.JSONData, config.Data) c.JSON(code, data) case binding.MIMEHTML: data := chooseData(config.HTMLData, config.Data) c.HTML(code, config.HTMLName, data) case binding.MIMEXML: data := chooseData(config.XMLData, config.Data) c.XML(code, data) case binding.MIMEYAML: data := chooseData(config.YAMLData, config.Data) c.YAML(code, data) default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } } // NegotiateFormat returns an acceptable Accept format. func (c *Context) NegotiateFormat(offered ...string) string { assert1(len(offered) > 0, "you must provide at least one offer") if c.Accepted == nil { c.Accepted = parseAccept(c.requestHeader("Accept")) } if len(c.Accepted) == 0 { return offered[0] } for _, accepted := range c.Accepted { for _, offer := range offered { // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, // therefore we can just iterate over the string without casting it into []rune i := 0 for ; i < len(accepted); i++ { if accepted[i] == '*' || offer[i] == '*' { return offer } if accepted[i] != offer[i] { break } } if i == len(accepted) { return offer } } } return "" } // SetAccepted sets Accept header data. func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } /************************************/ /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ // Deadline always returns that there is no deadline (ok==false), // maybe you want to use Request.Context().Deadline() instead. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } // Done always returns nil (chan which will wait forever), // if you want to abort your work when the connection was closed // you should use Request.Context().Done() instead. func (c *Context) Done() <-chan struct{} { return nil } // Err always returns nil, maybe you want to use Request.Context().Err() instead. func (c *Context) Err() error { return nil } // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. func (c *Context) Value(key interface{}) interface{} { if key == 0 { return c.Request } if keyAsString, ok := key.(string); ok { val, _ := c.Get(keyAsString) return val } return nil } gin-1.6.3/context_appengine.go000066400000000000000000000003631365354716400163570ustar00rootroot00000000000000// +build appengine // Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin func init() { defaultAppEngine = true } gin-1.6.3/context_test.go000066400000000000000000001636371365354716400154060ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "context" "errors" "fmt" "html/template" "io" "mime/multipart" "net/http" "net/http/httptest" "os" "reflect" "strings" "sync" "testing" "time" "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) var _ context.Context = &Context{} // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { // BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) { // test that information is not leaked when reusing Contexts (using the Pool) func createMultipartRequest() *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() must(mw.SetBoundary(boundary)) must(mw.WriteField("foo", "bar")) must(mw.WriteField("bar", "10")) must(mw.WriteField("bar", "foo2")) must(mw.WriteField("array", "first")) must(mw.WriteField("array", "second")) must(mw.WriteField("id", "")) must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) must(mw.WriteField("time_location", "31/12/2016 14:55")) must(mw.WriteField("names[a]", "thinkerou")) must(mw.WriteField("names[b]", "tianou")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func must(err error) { if err != nil { panic(err.Error()) } } func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { _, err = w.Write([]byte("test")) assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") if assert.NoError(t, err) { assert.Equal(t, "test", f.Filename) } assert.NoError(t, c.SaveUploadedFile(f, "test")) } func TestContextFormFileFailed(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 f, err := c.FormFile("file") assert.Error(t, err) assert.Nil(t, f) } func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) assert.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { _, err = w.Write([]byte("test")) assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.MultipartForm() if assert.NoError(t, err) { assert.NotNil(t, f) } assert.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) } func TestSaveUploadedOpenFailed(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f := &multipart.FileHeader{ Filename: "file", } assert.Error(t, c.SaveUploadedFile(f, "test")) } func TestSaveUploadedCreateFailed(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { _, err = w.Write([]byte("test")) assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") if assert.NoError(t, err) { assert.Equal(t, "test", f.Filename) } assert.Error(t, c.SaveUploadedFile(f, "/")) } func TestContextReset(t *testing.T) { router := New() c := router.allocateContext() assert.Equal(t, c.engine, router) c.index = 2 c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Params = Params{Param{}} c.Error(errors.New("test")) // nolint: errcheck c.Set("foo", "bar") c.reset() assert.False(t, c.IsAborted()) assert.Nil(t, c.Keys) assert.Nil(t, c.Accepted) assert.Len(t, c.Errors, 0) assert.Empty(t, c.Errors.Errors()) assert.Empty(t, c.Errors.ByType(ErrorTypeAny)) assert.Len(t, c.Params, 0) assert.EqualValues(t, c.index, -1) assert.Equal(t, c.Writer.(*responseWriter), &c.writermem) } func TestContextHandlers(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Nil(t, c.handlers) assert.Nil(t, c.handlers.Last()) c.handlers = HandlersChain{} assert.NotNil(t, c.handlers) assert.Nil(t, c.handlers.Last()) f := func(c *Context) {} g := func(c *Context) {} c.handlers = HandlersChain{f} compareFunc(t, f, c.handlers.Last()) c.handlers = HandlersChain{f, g} compareFunc(t, g, c.handlers.Last()) } // TestContextSetGet tests that a parameter is set correctly on the // current context and can be retrieved using Get. func TestContextSetGet(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("foo", "bar") value, err := c.Get("foo") assert.Equal(t, "bar", value) assert.True(t, err) value, err = c.Get("foo2") assert.Nil(t, value) assert.False(t, err) assert.Equal(t, "bar", c.MustGet("foo")) assert.Panics(t, func() { c.MustGet("no_exist") }) } func TestContextSetGetValues(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("string", "this is a string") c.Set("int32", int32(-42)) c.Set("int64", int64(42424242424242)) c.Set("uint64", uint64(42)) c.Set("float32", float32(4.2)) c.Set("float64", 4.2) var a interface{} = 1 c.Set("intInterface", a) assert.Exactly(t, c.MustGet("string").(string), "this is a string") assert.Exactly(t, c.MustGet("int32").(int32), int32(-42)) assert.Exactly(t, c.MustGet("int64").(int64), int64(42424242424242)) assert.Exactly(t, c.MustGet("uint64").(uint64), uint64(42)) assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2)) assert.Exactly(t, c.MustGet("float64").(float64), 4.2) assert.Exactly(t, c.MustGet("intInterface").(int), 1) } func TestContextGetString(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("string", "this is a string") assert.Equal(t, "this is a string", c.GetString("string")) } func TestContextSetGetBool(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("bool", true) assert.True(t, c.GetBool("bool")) } func TestContextGetInt(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int", 1) assert.Equal(t, 1, c.GetInt("int")) } func TestContextGetInt64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int64", int64(42424242424242)) assert.Equal(t, int64(42424242424242), c.GetInt64("int64")) } func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) assert.Equal(t, 4.2, c.GetFloat64("float64")) } func TestContextGetTime(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) t1, _ := time.Parse("1/2/2006 15:04:05", "01/01/2017 12:00:00") c.Set("time", t1) assert.Equal(t, t1, c.GetTime("time")) } func TestContextGetDuration(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("duration", time.Second) assert.Equal(t, time.Second, c.GetDuration("duration")) } func TestContextGetStringSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("slice", []string{"foo"}) assert.Equal(t, []string{"foo"}, c.GetStringSlice("slice")) } func TestContextGetStringMap(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) var m = make(map[string]interface{}) m["foo"] = 1 c.Set("map", m) assert.Equal(t, m, c.GetStringMap("map")) assert.Equal(t, 1, c.GetStringMap("map")["foo"]) } func TestContextGetStringMapString(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) var m = make(map[string]string) m["foo"] = "bar" c.Set("map", m) assert.Equal(t, m, c.GetStringMapString("map")) assert.Equal(t, "bar", c.GetStringMapString("map")["foo"]) } func TestContextGetStringMapStringSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) var m = make(map[string][]string) m["foo"] = []string{"foo"} c.Set("map", m) assert.Equal(t, m, c.GetStringMapStringSlice("map")) assert.Equal(t, []string{"foo"}, c.GetStringMapStringSlice("map")["foo"]) } func TestContextCopy(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 c.Request, _ = http.NewRequest("POST", "/hola", nil) c.handlers = HandlersChain{func(c *Context) {}} c.Params = Params{Param{Key: "foo", Value: "bar"}} c.Set("foo", "bar") cp := c.Copy() assert.Nil(t, cp.handlers) assert.Nil(t, cp.writermem.ResponseWriter) assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter)) assert.Equal(t, cp.Request, c.Request) assert.Equal(t, cp.index, abortIndex) assert.Equal(t, cp.Keys, c.Keys) assert.Equal(t, cp.engine, c.engine) assert.Equal(t, cp.Params, c.Params) cp.Set("foo", "notBar") assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) } func TestContextHandlerName(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest} assert.Regexp(t, "^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$", c.HandlerName()) } func TestContextHandlerNames(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2} names := c.HandlerNames() assert.True(t, len(names) == 4) for _, name := range names { assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name) } } func handlerNameTest(c *Context) { } func handlerNameTest2(c *Context) { } var handlerTest HandlerFunc = func(c *Context) { } func TestContextHandler(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.handlers = HandlersChain{func(c *Context) {}, handlerTest} assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer()) } func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) value, ok := c.GetQuery("foo") assert.True(t, ok) assert.Equal(t, "bar", value) assert.Equal(t, "bar", c.DefaultQuery("foo", "none")) assert.Equal(t, "bar", c.Query("foo")) value, ok = c.GetQuery("page") assert.True(t, ok) assert.Equal(t, "10", value) assert.Equal(t, "10", c.DefaultQuery("page", "0")) assert.Equal(t, "10", c.Query("page")) value, ok = c.GetQuery("id") assert.True(t, ok) assert.Empty(t, value) assert.Empty(t, c.DefaultQuery("id", "nada")) assert.Empty(t, c.Query("id")) value, ok = c.GetQuery("NoKey") assert.False(t, ok) assert.Empty(t, value) assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada")) assert.Empty(t, c.Query("NoKey")) // postform should not mess value, ok = c.GetPostForm("page") assert.False(t, ok) assert.Empty(t, value) assert.Empty(t, c.PostForm("foo")) } func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) assert.Equal(t, "bar", c.PostForm("foo")) assert.Empty(t, c.Query("foo")) value, ok := c.GetPostForm("page") assert.True(t, ok) assert.Equal(t, "11", value) assert.Equal(t, "11", c.DefaultPostForm("page", "0")) assert.Equal(t, "11", c.PostForm("page")) assert.Empty(t, c.Query("page")) value, ok = c.GetPostForm("both") assert.True(t, ok) assert.Empty(t, value) assert.Empty(t, c.PostForm("both")) assert.Empty(t, c.DefaultPostForm("both", "nothing")) assert.Equal(t, "GET", c.Query("both"), "GET") value, ok = c.GetQuery("id") assert.True(t, ok) assert.Equal(t, "main", value) assert.Equal(t, "000", c.DefaultPostForm("id", "000")) assert.Equal(t, "main", c.Query("id")) assert.Empty(t, c.PostForm("id")) value, ok = c.GetQuery("NoKey") assert.False(t, ok) assert.Empty(t, value) value, ok = c.GetPostForm("NoKey") assert.False(t, ok) assert.Empty(t, value) assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada")) assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing")) assert.Empty(t, c.PostForm("NoKey")) assert.Empty(t, c.Query("NoKey")) var obj struct { Foo string `form:"foo"` ID string `form:"id"` Page int `form:"page"` Both string `form:"both"` Array []string `form:"array[]"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo, "bar") assert.Equal(t, "main", obj.ID, "main") assert.Equal(t, 11, obj.Page, 11) assert.Empty(t, obj.Both) assert.Equal(t, []string{"first", "second"}, obj.Array) values, ok := c.GetQueryArray("array[]") assert.True(t, ok) assert.Equal(t, "first", values[0]) assert.Equal(t, "second", values[1]) values = c.QueryArray("array[]") assert.Equal(t, "first", values[0]) assert.Equal(t, "second", values[1]) values = c.QueryArray("nokey") assert.Equal(t, 0, len(values)) values = c.QueryArray("both") assert.Equal(t, 1, len(values)) assert.Equal(t, "GET", values[0]) dicts, ok := c.GetQueryMap("ids") assert.True(t, ok) assert.Equal(t, "hi", dicts["a"]) assert.Equal(t, "3.14", dicts["b"]) dicts, ok = c.GetQueryMap("nokey") assert.False(t, ok) assert.Equal(t, 0, len(dicts)) dicts, ok = c.GetQueryMap("both") assert.False(t, ok) assert.Equal(t, 0, len(dicts)) dicts, ok = c.GetQueryMap("array") assert.False(t, ok) assert.Equal(t, 0, len(dicts)) dicts = c.QueryMap("ids") assert.Equal(t, "hi", dicts["a"]) assert.Equal(t, "3.14", dicts["b"]) dicts = c.QueryMap("nokey") assert.Equal(t, 0, len(dicts)) } func TestContextPostFormMultipart(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request = createMultipartRequest() var obj struct { Foo string `form:"foo"` Bar string `form:"bar"` BarAsInt int `form:"bar"` Array []string `form:"array"` ID string `form:"id"` TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"` BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "10", obj.Bar) assert.Equal(t, 10, obj.BarAsInt) assert.Equal(t, []string{"first", "second"}, obj.Array) assert.Empty(t, obj.ID) assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04")) assert.Equal(t, time.Local, obj.TimeLocal.Location()) assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04")) assert.Equal(t, time.UTC, obj.TimeUTC.Location()) loc, _ := time.LoadLocation("Asia/Tokyo") assert.Equal(t, "31/12/2016 14:55", obj.TimeLocation.Format("02/01/2006 15:04")) assert.Equal(t, loc, obj.TimeLocation.Location()) assert.True(t, obj.BlankTime.IsZero()) value, ok := c.GetQuery("foo") assert.False(t, ok) assert.Empty(t, value) assert.Empty(t, c.Query("bar")) assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing")) value, ok = c.GetPostForm("foo") assert.True(t, ok) assert.Equal(t, "bar", value) assert.Equal(t, "bar", c.PostForm("foo")) value, ok = c.GetPostForm("array") assert.True(t, ok) assert.Equal(t, "first", value) assert.Equal(t, "first", c.PostForm("array")) assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing")) value, ok = c.GetPostForm("id") assert.True(t, ok) assert.Empty(t, value) assert.Empty(t, c.PostForm("id")) assert.Empty(t, c.DefaultPostForm("id", "nothing")) value, ok = c.GetPostForm("nokey") assert.False(t, ok) assert.Empty(t, value) assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing")) values, ok := c.GetPostFormArray("array") assert.True(t, ok) assert.Equal(t, "first", values[0]) assert.Equal(t, "second", values[1]) values = c.PostFormArray("array") assert.Equal(t, "first", values[0]) assert.Equal(t, "second", values[1]) values = c.PostFormArray("nokey") assert.Equal(t, 0, len(values)) values = c.PostFormArray("foo") assert.Equal(t, 1, len(values)) assert.Equal(t, "bar", values[0]) dicts, ok := c.GetPostFormMap("names") assert.True(t, ok) assert.Equal(t, "thinkerou", dicts["a"]) assert.Equal(t, "tianou", dicts["b"]) dicts, ok = c.GetPostFormMap("nokey") assert.False(t, ok) assert.Equal(t, 0, len(dicts)) dicts = c.PostFormMap("names") assert.Equal(t, "thinkerou", dicts["a"]) assert.Equal(t, "tianou", dicts["b"]) dicts = c.PostFormMap("nokey") assert.Equal(t, 0, len(dicts)) } func TestContextSetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.SetSameSite(http.SameSiteLaxMode) c.SetCookie("user", "gin", 1, "/", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } func TestContextSetCookiePathEmpty(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.SetSameSite(http.SameSiteLaxMode) c.SetCookie("user", "gin", 1, "", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } func TestContextGetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") assert.Equal(t, "gin", cookie) _, err := c.Cookie("nokey") assert.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing)) assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestPanicRender struct { } func (*TestPanicRender) Render(http.ResponseWriter) error { return errors.New("TestPanicRender") } func (*TestPanicRender) WriteContentType(http.ResponseWriter) {} func TestContextRenderPanicIfErr(t *testing.T) { defer func() { r := recover() assert.Equal(t, "TestPanicRender", fmt.Sprint(r)) }() w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Render(http.StatusOK, &TestPanicRender{}) assert.Fail(t, "Panic not detected") } // Tests that the response is serialized as JSON // and Content-Type is set to application/json // and special HTML characters are escaped func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP // and Content-Type is set to application/javascript func TestContextRenderJSONP(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"});", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP // and Content-Type is set to application/json func TestContextRenderJSONPWithoutCallback(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.JSON(http.StatusNoContent, H{"foo": "bar"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON // we change the content-type before func TestContextRenderAPIJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 func TestContextRenderNoContentAPIJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") c.JSON(http.StatusNoContent, H{"foo": "bar"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json") } // Tests that the response is serialized as JSON // and Content-Type is set to application/json func TestContextRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 func TestContextRenderNoContentIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as Secure JSON // and Content-Type is set to application/json func TestContextRenderSecureJSON(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) router.SecureJsonPrefix("&&&START&&&") c.SecureJSON(http.StatusCreated, []string{"foo", "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 func TestContextRenderNoContentSecureJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderNoContentAsciiJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON // and Content-Type is set to application/json // and special HTML characters are preserved func TestContextRenderPureJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderHTML2(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) // print debug warning log when Engine.trees > 0 router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) re := captureOutput(t, func() { SetMode(DebugMode) router.SetHTMLTemplate(templ) SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML is rendered if code is 204 func TestContextRenderNoContentHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextXML tests that the response is serialized as XML // and Content-Type is set to application/xml func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.XML(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no XML is rendered if code is 204 func TestContextRenderNoContentXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.XML(http.StatusNoContent, H{"foo": "bar"}) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.String(http.StatusCreated, "test %s %d", "string", 2) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no String is rendered if code is 204 func TestContextRenderNoContentString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.String(http.StatusNoContent, "test %s %d", "string", 2) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned // with Content-Type set to text/html func TestContextRenderHTMLString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") c.String(http.StatusCreated, "%s %d", "string", 3) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML String is rendered if code is 204 func TestContextRenderNoContentHTMLString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") c.String(http.StatusNoContent, "%s %d", "string", 3) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextData tests that the response can be written from `bytesting` // with specified MIME type func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`)) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } // Tests that no Custom Data is rendered if code is 204 func TestContextRenderNoContentData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`)) assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } func TestContextRenderSSE(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.SSEvent("float", 1.5) c.Render(-1, sse.Event{ Id: "123", Data: "text", }) c.SSEvent("chat", H{ "foo": "bar", "bar": "foo", }) assert.Equal(t, strings.Replace(w.Body.String(), " ", "", -1), strings.Replace("event:float\ndata:1.5\n\nid:123\ndata:text\n\nevent:chat\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n", " ", "", -1)) } func TestContextRenderFile(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderFileFromFS(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "/some/path", nil) c.FileFromFS("./gin.go", Dir(".", false)) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "/some/path", c.Request.URL.Path) } func TestContextRenderAttachment(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) newFilename := "new_filename.go" c.Request, _ = http.NewRequest("GET", "/", nil) c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) } // TestContextRenderYAML tests that the response is serialized as YAML // and Content-Type is set to application/x-yaml func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.YAML(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf // and Content-Type is set to application/x-protobuf // and we just use the example protobuf to check if the response is correct func TestContextRenderProtoBuf(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) reps := []int64{int64(1), int64(2)} label := "test" data := &testdata.Test{ Label: &label, Reps: reps, } c.ProtoBuf(http.StatusCreated, data) protoData, err := proto.Marshal(data) assert.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } func TestContextHeaders(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Header("Content-Type", "text/plain") c.Header("X-Custom", "value") assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type")) assert.Equal(t, "value", c.Writer.Header().Get("X-Custom")) c.Header("Content-Type", "text/html") c.Header("X-Custom", "") assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type")) _, exist := c.Writer.Header()["X-Custom"] assert.False(t, exist) } // TODO func TestContextRenderRedirectWithRelativePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) c.Redirect(http.StatusMovedPermanently, "/path") c.Writer.WriteHeaderNow() assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, "/path", w.Header().Get("Location")) } func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() assert.Equal(t, http.StatusFound, w.Code) assert.Equal(t, "http://google.com", w.Header().Get("Location")) } func TestContextRenderRedirectWith201(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "/resource", w.Header().Get("Location")) } func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(309, "/resource") }) assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") }) assert.NotPanics(t, func() { c.Redirect(http.StatusPermanentRedirect, "/resource") }) } func TestContextNegotiationWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML, MIMEYAML}, Data: H{"foo": "bar"}, }) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON, MIMEYAML}, Data: H{"foo": "bar"}, }) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEHTML}, Data: H{"name": "gin"}, HTMLName: "t", }) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, }) assert.Equal(t, http.StatusNotAcceptable, w.Code) assert.Equal(t, c.index, abortIndex) assert.True(t, c.IsAborted()) } func TestContextNegotiationFormat(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) } func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "*/*") assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) c, _ = CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/*") assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("application/*"), "") assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") assert.Equal(t, c.NegotiateFormat(MIMEXML), "") assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) } func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) } func TestContextIsAborted(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.False(t, c.IsAborted()) c.Abort() assert.True(t, c.IsAborted()) c.Next() assert.True(t, c.IsAborted()) c.index++ assert.True(t, c.IsAborted()) } // TestContextData tests that the response can be written from `bytesting` // with specified MIME type func TestContextAbortWithStatus(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.index = 4 c.AbortWithStatus(http.StatusUnauthorized) assert.Equal(t, abortIndex, c.index) assert.Equal(t, http.StatusUnauthorized, c.Writer.Status()) assert.Equal(t, http.StatusUnauthorized, w.Code) assert.True(t, c.IsAborted()) } type testJSONAbortMsg struct { Foo string `json:"foo"` Bar string `json:"bar"` } func TestContextAbortWithStatusJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.index = 4 in := new(testJSONAbortMsg) in.Bar = "barValue" in.Foo = "fooValue" c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in) assert.Equal(t, abortIndex, c.index) assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) assert.True(t, c.IsAborted()) contentType := w.Header().Get("Content-Type") assert.Equal(t, "application/json; charset=utf-8", contentType) buf := new(bytes.Buffer) _, err := buf.ReadFrom(w.Body) assert.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody) } func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) firstErr := errors.New("first error") c.Error(firstErr) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) secondErr := errors.New("second error") c.Error(&Error{ // nolint: errcheck Err: secondErr, Meta: "some data 2", Type: ErrorTypePublic, }) assert.Len(t, c.Errors, 2) assert.Equal(t, firstErr, c.Errors[0].Err) assert.Nil(t, c.Errors[0].Meta) assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type) assert.Equal(t, secondErr, c.Errors[1].Err) assert.Equal(t, "some data 2", c.Errors[1].Meta) assert.Equal(t, ErrorTypePublic, c.Errors[1].Type) assert.Equal(t, c.Errors.Last(), c.Errors[1]) defer func() { if recover() == nil { t.Error("didn't panic") } }() c.Error(nil) // nolint: errcheck } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck for _, err := range c.Errors.ByType(ErrorTypePublic) { assert.Equal(t, ErrorTypePublic, err.Type) } for _, err := range c.Errors.ByType(ErrorTypePrivate) { assert.Equal(t, ErrorTypePrivate, err.Type) } assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors()) } func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.RemoteAddr = " 40.40.40.40:42123 " assert.Equal(t, "20.20.20.20", c.ClientIP()) c.Request.Header.Del("X-Forwarded-For") assert.Equal(t, "10.10.10.10", c.ClientIP()) c.Request.Header.Set("X-Forwarded-For", "30.30.30.30 ") assert.Equal(t, "30.30.30.30", c.ClientIP()) c.Request.Header.Del("X-Forwarded-For") c.Request.Header.Del("X-Real-IP") c.engine.AppEngine = true assert.Equal(t, "50.50.50.50", c.ClientIP()) c.Request.Header.Del("X-Appengine-Remote-Addr") assert.Equal(t, "40.40.40.40", c.ClientIP()) // no port c.Request.RemoteAddr = "50.50.50.50" assert.Empty(t, c.ClientIP()) } func TestContextContentType(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") assert.Equal(t, "application/json", c.ContentType()) } func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) } func TestContextBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.NoError(t, c.BindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` FOO BAR `)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `xml:"foo"` Bar string `xml:"bar"` } assert.NoError(t, c.BindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) } func TestContextBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") var testHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` Limit int `header:"limit"` } assert.NoError(t, c.BindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) assert.Equal(t, 0, w.Body.Len()) } func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` Bar string `form:"bar"` } assert.NoError(t, c.BindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } func TestContextBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } assert.NoError(t, c.BindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.False(t, c.IsAborted()) assert.Error(t, c.Bind(&obj)) c.Writer.WriteHeaderNow() assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) assert.Equal(t, http.StatusBadRequest, w.Code) assert.True(t, c.IsAborted()) } func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.NoError(t, c.ShouldBind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) } func TestContextShouldBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.NoError(t, c.ShouldBindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } func TestContextShouldBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` FOO BAR `)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `xml:"foo"` Bar string `xml:"bar"` } assert.NoError(t, c.ShouldBindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) } func TestContextShouldBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") var testHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` Limit int `header:"limit"` } assert.NoError(t, c.ShouldBindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) assert.Equal(t, 0, w.Body.Len()) } func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` Bar string `form:"bar"` Foo1 string `form:"Foo"` Bar1 string `form:"Bar"` } assert.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo1", obj.Bar1) assert.Equal(t, "bar1", obj.Foo1) assert.Equal(t, 0, w.Body.Len()) } func TestContextShouldBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } assert.NoError(t, c.ShouldBindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` Bar string `json:"bar"` } assert.False(t, c.IsAborted()) assert.Error(t, c.ShouldBind(&obj)) assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) assert.False(t, c.IsAborted()) } func TestContextShouldBindBodyWith(t *testing.T) { type typeA struct { Foo string `json:"foo" xml:"foo" binding:"required"` } type typeB struct { Bar string `json:"bar" xml:"bar" binding:"required"` } for _, tt := range []struct { name string bindingA, bindingB binding.BindingBody bodyA, bodyB string }{ { name: "JSON & JSON", bindingA: binding.JSON, bindingB: binding.JSON, bodyA: `{"foo":"FOO"}`, bodyB: `{"bar":"BAR"}`, }, { name: "JSON & XML", bindingA: binding.JSON, bindingB: binding.XML, bodyA: `{"foo":"FOO"}`, bodyB: ` BAR `, }, { name: "XML & XML", bindingA: binding.XML, bindingB: binding.XML, bodyA: ` FOO `, bodyB: ` BAR `, }, } { t.Logf("testing: %s", tt.name) // bodyA to typeA and typeB { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), ) // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. objA := typeA{} assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.Equal(t, typeA{"FOO"}, objA) objB := typeB{} assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.NotEqual(t, typeB{"BAR"}, objB) } // bodyB to typeA and typeB { // When it binds to typeA and typeB, it finds the body is // not typeA but typeB. w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), ) objA := typeA{} assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.NotEqual(t, typeA{"FOO"}, objA) objB := typeB{} assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.Equal(t, typeB{"BAR"}, objB) } } } func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) assert.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() assert.Equal(t, ti, time.Time{}) assert.False(t, ok) assert.Equal(t, c.Value(0), c.Request) assert.Nil(t, c.Value("foo")) c.Set("foo", "bar") assert.Equal(t, "bar", c.Value("foo")) assert.Nil(t, c.Value(1)) } func TestWebsocketsRequired(t *testing.T) { // Example request from spec: https://tools.ietf.org/html/rfc6455#section-1.2 c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/chat", nil) c.Request.Header.Set("Host", "server.example.com") c.Request.Header.Set("Upgrade", "websocket") c.Request.Header.Set("Connection", "Upgrade") c.Request.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==") c.Request.Header.Set("Origin", "http://example.com") c.Request.Header.Set("Sec-WebSocket-Protocol", "chat, superchat") c.Request.Header.Set("Sec-WebSocket-Version", "13") assert.True(t, c.IsWebsocket()) // Normal request, no websocket required. c, _ = CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/chat", nil) c.Request.Header.Set("Host", "server.example.com") assert.False(t, c.IsWebsocket()) } func TestGetRequestHeaderValue(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/chat", nil) c.Request.Header.Set("Gin-Version", "1.0.0") assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) assert.Empty(t, c.GetHeader("Connection")) } func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("Fetch binary post data") c.Request, _ = http.NewRequest("POST", "/", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) data, err := c.GetRawData() assert.Nil(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } func TestContextRenderDataFromReader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) body := "#!PNG some raw data" reader := strings.NewReader(body) contentLength := int64(len(body)) contentType := "image/png" extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`} c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } func TestContextRenderDataFromReaderNoHeaders(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) body := "#!PNG some raw data" reader := strings.NewReader(body) contentLength := int64(len(body)) contentType := "image/png" c.DataFromReader(http.StatusOK, contentLength, contentType, reader, nil) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) } type TestResponseRecorder struct { *httptest.ResponseRecorder closeChannel chan bool } func (r *TestResponseRecorder) CloseNotify() <-chan bool { return r.closeChannel } func (r *TestResponseRecorder) closeClient() { r.closeChannel <- true } func CreateTestResponseRecorder() *TestResponseRecorder { return &TestResponseRecorder{ httptest.NewRecorder(), make(chan bool, 1), } } func TestContextStream(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) stopStream := true c.Stream(func(w io.Writer) bool { defer func() { stopStream = false }() _, err := w.Write([]byte("test")) assert.NoError(t, err) return stopStream }) assert.Equal(t, "testtest", w.Body.String()) } func TestContextStreamWithClientGone(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) c.Stream(func(writer io.Writer) bool { defer func() { w.closeClient() }() _, err := writer.Write([]byte("test")) assert.NoError(t, err) return true }) assert.Equal(t, "test", w.Body.String()) } func TestContextResetInHandler(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) c.handlers = []HandlerFunc{ func(c *Context) { c.reset() }, } assert.NotPanics(t, func() { c.Next() }) } func TestRaceParamsContextCopy(t *testing.T) { DefaultWriter = os.Stdout router := Default() nameGroup := router.Group("/:name") var wg sync.WaitGroup wg.Add(2) { nameGroup.GET("/api", func(c *Context) { go func(c *Context, param string) { defer wg.Done() // First assert must be executed after the second request time.Sleep(50 * time.Millisecond) assert.Equal(t, c.Param("name"), param) }(c.Copy(), c.Param("name")) }) } performRequest(router, "GET", "/name1/api") performRequest(router, "GET", "/name2/api") wg.Wait() } func TestContextWithKeysMutex(t *testing.T) { c := &Context{} c.Set("foo", "bar") value, err := c.Get("foo") assert.Equal(t, "bar", value) assert.True(t, err) value, err = c.Get("foo2") assert.Nil(t, value) assert.False(t, err) } gin-1.6.3/debug.go000066400000000000000000000053561365354716400137420ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "html/template" "runtime" "strconv" "strings" ) const ginSupportMinGoVer = 10 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { return ginMode == debugCode } // DebugPrintRouteFunc indicates debug log output format. var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) handlerName := nameOfFunction(handlers.Last()) if DebugPrintRouteFunc == nil { debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) } else { DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers) } } } func debugPrintLoadTemplate(tmpl *template.Template) { if IsDebugging() { var buf strings.Builder for _, tmpl := range tmpl.Templates() { buf.WriteString("\t- ") buf.WriteString(tmpl.Name()) buf.WriteString("\n") } debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String()) } } func debugPrint(format string, values ...interface{}) { if IsDebugging() { if !strings.HasSuffix(format, "\n") { format += "\n" } fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) } } func getMinVer(v string) (uint64, error) { first := strings.IndexByte(v, '.') last := strings.LastIndexByte(v, '.') if first == last { return strconv.ParseUint(v[first+1:], 10, 64) } return strconv.ParseUint(v[first+1:last], 10, 64) } func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon. `) } debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) } func debugPrintWARNINGNew() { debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) `) } func debugPrintWARNINGSetHTMLTemplate() { debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called at initialization. ie. before any route is registered or the router is listening in a socket: router := gin.Default() router.SetHTMLTemplate(template) // << good place `) } func debugPrintError(err error) { if err != nil { if IsDebugging() { fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err) } } } gin-1.6.3/debug_test.go000066400000000000000000000105071365354716400147730ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "errors" "html/template" "io" "log" "os" "runtime" "sync" "testing" "github.com/stretchr/testify/assert" ) // TODO // func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { // func debugPrint(format string, values ...interface{}) { func TestIsDebugging(t *testing.T) { SetMode(DebugMode) assert.True(t, IsDebugging()) SetMode(ReleaseMode) assert.False(t, IsDebugging()) SetMode(TestMode) assert.False(t, IsDebugging()) } func TestDebugPrint(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) SetMode(ReleaseMode) debugPrint("DEBUG this!") SetMode(TestMode) debugPrint("DEBUG this!") SetMode(DebugMode) debugPrint("these are %d %s", 2, "error messages") SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) } func TestDebugPrintError(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) debugPrintError(nil) debugPrintError(errors.New("this is an error")) SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", re) } func TestDebugPrintRoutes(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) } func TestDebugPrintLoadTemplate(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, re) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGSetHTMLTemplate() SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) } func TestDebugPrintWARNINGDefault(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGDefault() SetMode(TestMode) }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } } func TestDebugPrintWARNINGNew(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGNew() SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } func captureOutput(t *testing.T, f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) } defaultWriter := DefaultWriter defaultErrorWriter := DefaultErrorWriter defer func() { DefaultWriter = defaultWriter DefaultErrorWriter = defaultErrorWriter log.SetOutput(os.Stderr) }() DefaultWriter = writer DefaultErrorWriter = writer log.SetOutput(writer) out := make(chan string) wg := new(sync.WaitGroup) wg.Add(1) go func() { var buf bytes.Buffer wg.Done() _, err := io.Copy(&buf, reader) assert.NoError(t, err) out <- buf.String() }() wg.Wait() f() writer.Close() return <-out } func TestGetMinVer(t *testing.T) { var m uint64 var e error _, e = getMinVer("go1") assert.NotNil(t, e) m, e = getMinVer("go1.1") assert.Equal(t, uint64(1), m) assert.Nil(t, e) m, e = getMinVer("go1.1.1") assert.Nil(t, e) assert.Equal(t, uint64(1), m) _, e = getMinVer("go1.1.1.1") assert.NotNil(t, e) } gin-1.6.3/deprecated.go000066400000000000000000000013251365354716400147440ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "log" "github.com/gin-gonic/gin/binding" ) // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) BindWith(obj interface{}, b binding.Binding) error { log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) return c.MustBindWith(obj, b) } gin-1.6.3/deprecated_test.go000066400000000000000000000014041365354716400160010ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" ) func TestBindWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` Bar string `form:"bar"` } captureOutput(t, func() { assert.NoError(t, c.BindWith(&obj, binding.Form)) }) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } gin-1.6.3/doc.go000066400000000000000000000002531365354716400134100ustar00rootroot00000000000000/* Package gin implements a HTTP web framework called gin. See https://gin-gonic.com/ for more information about gin. */ package gin // import "github.com/gin-gonic/gin" gin-1.6.3/errors.go000066400000000000000000000073541365354716400141700ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "reflect" "strings" "github.com/gin-gonic/gin/internal/json" ) // ErrorType is an unsigned 64-bit error code as defined in the gin spec. type ErrorType uint64 const ( // ErrorTypeBind is used when Context.Bind() fails. ErrorTypeBind ErrorType = 1 << 63 // ErrorTypeRender is used when Context.Render() fails. ErrorTypeRender ErrorType = 1 << 62 // ErrorTypePrivate indicates a private error. ErrorTypePrivate ErrorType = 1 << 0 // ErrorTypePublic indicates a public error. ErrorTypePublic ErrorType = 1 << 1 // ErrorTypeAny indicates any other error. ErrorTypeAny ErrorType = 1<<64 - 1 // ErrorTypeNu indicates any other error. ErrorTypeNu = 2 ) // Error represents a error's specification. type Error struct { Err error Type ErrorType Meta interface{} } type errorMsgs []*Error var _ error = &Error{} // SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { msg.Type = flags return msg } // SetMeta sets the error's meta data. func (msg *Error) SetMeta(data interface{}) *Error { msg.Meta = data return msg } // JSON creates a properly formatted JSON func (msg *Error) JSON() interface{} { json := H{} if msg.Meta != nil { value := reflect.ValueOf(msg.Meta) switch value.Kind() { case reflect.Struct: return msg.Meta case reflect.Map: for _, key := range value.MapKeys() { json[key.String()] = value.MapIndex(key).Interface() } default: json["meta"] = msg.Meta } } if _, ok := json["error"]; !ok { json["error"] = msg.Error() } return json } // MarshalJSON implements the json.Marshaller interface. func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } // Error implements the error interface. func (msg Error) Error() string { return msg.Err.Error() } // IsType judges one error. func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } // ByType returns a readonly copy filtered the byte. // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic. func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { return nil } if typ == ErrorTypeAny { return a } var result errorMsgs for _, msg := range a { if msg.IsType(typ) { result = append(result, msg) } } return result } // Last returns the last error in the slice. It returns nil if the array is empty. // Shortcut for errors[len(errors)-1]. func (a errorMsgs) Last() *Error { if length := len(a); length > 0 { return a[length-1] } return nil } // Errors returns an array will all the error messages. // Example: // c.Error(errors.New("first")) // c.Error(errors.New("second")) // c.Error(errors.New("third")) // c.Errors.Errors() // == []string{"first", "second", "third"} func (a errorMsgs) Errors() []string { if len(a) == 0 { return nil } errorStrings := make([]string, len(a)) for i, err := range a { errorStrings[i] = err.Error() } return errorStrings } func (a errorMsgs) JSON() interface{} { switch len(a) { case 0: return nil case 1: return a.Last().JSON() default: json := make([]interface{}, len(a)) for i, err := range a { json[i] = err.JSON() } return json } } // MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { return json.Marshal(a.JSON()) } func (a errorMsgs) String() string { if len(a) == 0 { return "" } var buffer strings.Builder for i, msg := range a { fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err) if msg.Meta != nil { fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta) } } return buffer.String() } gin-1.6.3/errors_test.go000066400000000000000000000061771365354716400152310ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "errors" "testing" "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" ) func TestError(t *testing.T) { baseError := errors.New("test error") err := &Error{ Err: baseError, Type: ErrorTypePrivate, } assert.Equal(t, err.Error(), baseError.Error()) assert.Equal(t, H{"error": baseError.Error()}, err.JSON()) assert.Equal(t, err.SetType(ErrorTypePublic), err) assert.Equal(t, ErrorTypePublic, err.Type) assert.Equal(t, err.SetMeta("some data"), err) assert.Equal(t, "some data", err.Meta) assert.Equal(t, H{ "error": baseError.Error(), "meta": "some data", }, err.JSON()) jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ // nolint: errcheck "status": "200", "data": "some data", }) assert.Equal(t, H{ "error": baseError.Error(), "status": "200", "data": "some data", }, err.JSON()) err.SetMeta(H{ // nolint: errcheck "error": "custom error", "status": "200", "data": "some data", }) assert.Equal(t, H{ "error": "custom error", "status": "200", "data": "some data", }, err.JSON()) type customError struct { status string data string } err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } func TestErrorSlice(t *testing.T) { errs := errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, {Err: errors.New("second"), Type: ErrorTypePrivate, Meta: "some data"}, {Err: errors.New("third"), Type: ErrorTypePublic, Meta: H{"status": "400"}}, } assert.Equal(t, errs, errs.ByType(ErrorTypeAny)) assert.Equal(t, "third", errs.Last().Error()) assert.Equal(t, []string{"first", "second", "third"}, errs.Errors()) assert.Equal(t, []string{"third"}, errs.ByType(ErrorTypePublic).Errors()) assert.Equal(t, []string{"first", "second"}, errs.ByType(ErrorTypePrivate).Errors()) assert.Equal(t, []string{"first", "second", "third"}, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors()) assert.Empty(t, errs.ByType(ErrorTypeBind)) assert.Empty(t, errs.ByType(ErrorTypeBind).String()) assert.Equal(t, `Error #01: first Error #02: second Meta: some data Error #03: third Meta: map[status:400] `, errs.String()) assert.Equal(t, []interface{}{ H{"error": "first"}, H{"error": "second", "meta": "some data"}, H{"error": "third", "status": "400"}, }, errs.JSON()) jsonBytes, _ := json.Marshal(errs) assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } assert.Equal(t, H{"error": "first"}, errs.JSON()) jsonBytes, _ = json.Marshal(errs) assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} assert.Nil(t, errs.Last()) assert.Nil(t, errs.JSON()) assert.Empty(t, errs.String()) } gin-1.6.3/examples/000077500000000000000000000000001365354716400141325ustar00rootroot00000000000000gin-1.6.3/examples/README.md000066400000000000000000000002201365354716400154030ustar00rootroot00000000000000# Gin Examples ⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples). gin-1.6.3/fs.go000066400000000000000000000021441365354716400132540ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "net/http" "os" ) type onlyfilesFS struct { fs http.FileSystem } type neuteredReaddirFile struct { http.File } // Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally // in router.Static(). // if listDirectory == true, then it works the same as http.Dir() otherwise it returns // a filesystem that prevents http.FileServer() to list the directory files. func Dir(root string, listDirectory bool) http.FileSystem { fs := http.Dir(root) if listDirectory { return fs } return &onlyfilesFS{fs} } // Open conforms to http.Filesystem. func (fs onlyfilesFS) Open(name string) (http.File, error) { f, err := fs.fs.Open(name) if err != nil { return nil, err } return neuteredReaddirFile{f}, nil } // Readdir overrides the http.File default implementation. func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil } gin-1.6.3/gin.go000066400000000000000000000360421365354716400134250ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "html/template" "net" "net/http" "os" "path" "sync" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" ) const defaultMultipartMemory = 32 << 20 // 32 MB var ( default404Body = []byte("404 page not found") default405Body = []byte("405 method not allowed") defaultAppEngine bool ) // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) // HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main one. func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] } return nil } // RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { Method string Path string Handler string HandlerFunc HandlerFunc } // RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Create an instance of Engine, by using New() or Default() type Engine struct { RouterGroup // Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool // If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. // See the PR #1817 and issue #1644 RemoveExtraSlash bool delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool trees methodTrees } var _ IRouter = &Engine{} // New returns a new blank Engine instance without any middleware attached. // By default the configuration is: // - RedirectTrailingSlash: true // - RedirectFixedPath: false // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine } func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } // Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine } // SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON. func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { engine.secureJsonPrefix = prefix return engine } // LoadHTMLGlob loads HTML files identified by glob pattern // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } engine.SetHTMLTemplate(templ) } // LoadHTMLFiles loads a slice of HTML files // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} return } templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } // SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { debugPrintWARNINGSetHTMLTemplate() } engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} } // SetFuncMap sets the FuncMap used for template.FuncMap. func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { engine.FuncMap = funcMap } // NoRoute adds handlers for NoRoute. It return a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.noRoute = handlers engine.rebuild404Handlers() } // NoMethod sets the handlers called when... TODO. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.noMethod = handlers engine.rebuild405Handlers() } // Use attaches a global middleware to the router. ie. the middleware attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (engine *Engine) rebuild404Handlers() { engine.allNoRoute = engine.combineHandlers(engine.noRoute) } func (engine *Engine) rebuild405Handlers() { engine.allNoMethod = engine.combineHandlers(engine.noMethod) } func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) } // Routes returns a slice of registered routes, including some useful information, such as: // the http method, path and the handler name. func (engine *Engine) Routes() (routes RoutesInfo) { for _, tree := range engine.trees { routes = iterate("", tree.method, routes, tree.root) } return routes } func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { path += root.path if len(root.handlers) > 0 { handlerFunc := root.handlers.Last() routes = append(routes, RouteInfo{ Method: method, Path: path, Handler: nameOfFunction(handlerFunc), HandlerFunc: handlerFunc, }) } for _, child := range root.children { routes = iterate(path, method, routes, child) } return routes } // Run attaches the router to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) return } // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { debugPrint("Listening and serving HTTPS on %s\n", addr) defer func() { debugPrintError(err) }() err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) return } // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file). // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) defer func() { debugPrintError(err) }() listener, err := net.Listen("unix", file) if err != nil { return } defer listener.Close() defer os.Remove(file) err = http.Serve(listener, engine) return } // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified file descriptor. // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunFd(fd int) (err error) { debugPrint("Listening and serving HTTP on fd@%d", fd) defer func() { debugPrintError(err) }() f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) listener, err := net.FileListener(f) if err != nil { return } defer listener.Close() err = engine.RunListener(listener) return } // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified net.Listener func (engine *Engine) RunListener(listener net.Listener) (err error) { debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) defer func() { debugPrintError(err) }() err = http.Serve(listener, engine) return } // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c) } // HandleContext re-enter a context that has been rewritten. // This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { oldIndexValue := c.index c.reset() engine.handleHTTPRequest(c) c.index = oldIndexValue } func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } if engine.RemoveExtraSlash { rPath = cleanPath(rPath) } // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree value := root.getValue(rPath, c.Params, unescape) if value.handlers != nil { c.handlers = value.handlers c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() if c.writermem.Written() { return } if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain _, err := c.Writer.Write(defaultMessage) if err != nil { debugPrint("cannot write message to writer during serve error: %v", err) } return } c.writermem.WriteHeaderNow() } func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { p = prefix + "/" + req.URL.Path } req.URL.Path = p + "/" if length := len(p); length > 1 && p[length-1] == '/' { req.URL.Path = p[:length-1] } redirectRequest(c) } func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request rPath := req.URL.Path if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { req.URL.Path = bytesconv.BytesToString(fixedPath) redirectRequest(c) return true } return false } func redirectRequest(c *Context) { req := c.Request rPath := req.URL.Path rURL := req.URL.String() code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != http.MethodGet { code = http.StatusTemporaryRedirect } debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) http.Redirect(c.Writer, req, rURL, code) c.writermem.WriteHeaderNow() } gin-1.6.3/ginS/000077500000000000000000000000001365354716400132145ustar00rootroot00000000000000gin-1.6.3/ginS/README.md000066400000000000000000000003721365354716400144750ustar00rootroot00000000000000# Gin Default Server This is API experiment for Gin. ```go package main import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/ginS" ) func main() { ginS.GET("/", func(c *gin.Context) { c.String(200, "Hello World") }) ginS.Run() } ``` gin-1.6.3/ginS/gins.go000066400000000000000000000127241365354716400145110ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package ginS import ( "html/template" "net/http" "sync" "github.com/gin-gonic/gin" ) var once sync.Once var internalEngine *gin.Engine func engine() *gin.Engine { once.Do(func() { internalEngine = gin.Default() }) return internalEngine } // LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { engine().LoadHTMLGlob(pattern) } // LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles. func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } // SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) } // NoRoute adds handlers for NoRoute. It return a 404 code by default. func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } // NoMethod is a wrapper for Engine.NoMethod. func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } // Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. // For example, all the routes that use a common middleware for authorization could be grouped. func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } // Handle is a wrapper for Engine.Handle. func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Handle(httpMethod, relativePath, handlers...) } // POST is a shortcut for router.Handle("POST", path, handle) func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().POST(relativePath, handlers...) } // GET is a shortcut for router.Handle("GET", path, handle) func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().GET(relativePath, handlers...) } // DELETE is a shortcut for router.Handle("DELETE", path, handle) func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().DELETE(relativePath, handlers...) } // PATCH is a shortcut for router.Handle("PATCH", path, handle) func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().PATCH(relativePath, handlers...) } // PUT is a shortcut for router.Handle("PUT", path, handle) func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().PUT(relativePath, handlers...) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().OPTIONS(relativePath, handlers...) } // HEAD is a shortcut for router.Handle("HEAD", path, handle) func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().HEAD(relativePath, handlers...) } // Any is a wrapper for Engine.Any. func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } // StaticFile is a wrapper for Engine.StaticFile. func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } // Static serves files from the given file system root. // Internally a http.FileServer is used, therefore http.NotFound is used instead // of the Router's NotFound handler. // To use the operating system's file system implementation, // use : // router.Static("/static", "/var/www") func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } // StaticFS is a wrapper for Engine.StaticFS. func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } // Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } // Routes returns a slice of registered routes. func Routes() gin.RoutesInfo { return engine().Routes() } // Run attaches to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func Run(addr ...string) (err error) { return engine().Run(addr...) } // RunTLS attaches to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } // RunUnix attaches to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) } // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified file descriptor. // Note: the method will block the calling goroutine indefinitely unless on error happens. func RunFd(fd int) (err error) { return engine().RunFd(fd) } gin-1.6.3/gin_integration_test.go000066400000000000000000000201251365354716400170620ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bufio" "crypto/tls" "fmt" "html/template" "io/ioutil" "net" "net/http" "net/http/httptest" "os" "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func testRequest(t *testing.T, url string) { tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } client := &http.Client{Transport: tr} resp, err := client.Get(url) assert.NoError(t, err) defer resp.Body.Close() body, ioerr := ioutil.ReadAll(resp.Body) assert.NoError(t, ioerr) assert.Equal(t, "it worked", string(body), "resp body should match") assert.Equal(t, "200 OK", resp.Status, "should get a 200") } func TestRunEmpty(t *testing.T) { os.Setenv("PORT", "") router := New() go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) assert.Error(t, router.Run(":8080")) testRequest(t, "http://localhost:8080/example") } func TestRunTLS(t *testing.T) { router := New() go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8443/example") } func TestPusher(t *testing.T) { var html = template.Must(template.New("https").Parse(` Https Test

Welcome, Ginner!

`)) router := New() router.Static("./assets", "./assets") router.SetHTMLTemplate(html) go func() { router.GET("/pusher", func(c *Context) { if pusher := c.Writer.Pusher(); pusher != nil { err := pusher.Push("/assets/app.js", nil) assert.NoError(t, err) } c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8449/pusher") } func TestRunEmptyWithEnv(t *testing.T) { os.Setenv("PORT", "3123") router := New() go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) assert.Error(t, router.Run(":3123")) testRequest(t, "http://localhost:3123/example") } func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { assert.NoError(t, router.Run("2", "2")) }) } func TestRunWithPort(t *testing.T) { router := New() go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run(":5150")) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) assert.Error(t, router.Run(":5150")) testRequest(t, "http://localhost:5150/example") } func TestUnixSocket(t *testing.T) { router := New() go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunUnix("/tmp/unix_unit_test")) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) c, err := net.Dial("unix", "/tmp/unix_unit_test") assert.NoError(t, err) fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) var response string for scanner.Scan() { response += scanner.Text() } assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } func TestBadUnixSocket(t *testing.T) { router := New() assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } func TestFileDescriptor(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) socketFile, err := listener.File() assert.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunFd(int(socketFile.Fd()))) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) assert.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) var response string for scanner.Scan() { response += scanner.Text() } assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } func TestBadFileDescriptor(t *testing.T) { router := New() assert.Error(t, router.RunFd(0)) } func TestListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunListener(listener)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) assert.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) var response string for scanner.Scan() { response += scanner.Text() } assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } func TestBadListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:10086") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) listener.Close() assert.Error(t, router.RunListener(listener)) } func TestWithHttptestWithAutoSelectedPort(t *testing.T) { router := New() router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) ts := httptest.NewServer(router) defer ts.Close() testRequest(t, ts.URL+"/example") } func TestConcurrentHandleContext(t *testing.T) { router := New() router.GET("/", func(c *Context) { c.Request.URL.Path = "/example" router.HandleContext(c) }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) var wg sync.WaitGroup iterations := 200 wg.Add(iterations) for i := 0; i < iterations; i++ { go func() { testGetRequestHandler(t, router, "/") wg.Done() }() } wg.Wait() } // func TestWithHttptestWithSpecifiedPort(t *testing.T) { // router := New() // router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) // l, _ := net.Listen("tcp", ":8033") // ts := httptest.Server{ // Listener: l, // Config: &http.Server{Handler: router}, // } // ts.Start() // defer ts.Close() // testRequest(t, "http://localhost:8033/example") // } func testGetRequestHandler(t *testing.T, h http.Handler, url string) { req, err := http.NewRequest(http.MethodGet, url, nil) assert.NoError(t, err) w := httptest.NewRecorder() h.ServeHTTP(w, req) assert.Equal(t, "it worked", w.Body.String(), "resp body should match") assert.Equal(t, 200, w.Code, "should get a 200") } gin-1.6.3/gin_test.go000066400000000000000000000324061365354716400144640ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "crypto/tls" "fmt" "html/template" "io/ioutil" "net/http" "net/http/httptest" "reflect" "strconv" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" ) func formatAsDate(t time.Time) string { year, month, day := t.Date() return fmt.Sprintf("%d/%02d/%02d", year, month, day) } func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server { SetMode(mode) defer SetMode(TestMode) var router *Engine captureOutput(t, func() { router = New() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) loadMethod(router) router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) router.GET("/raw", func(c *Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) }) var ts *httptest.Server if tls { ts = httptest.NewTLSServer(router) } else { ts = httptest.NewServer(router) } return ts } func TestLoadHTMLGlobDebugMode(t *testing.T) { ts := setupHTMLFiles( t, DebugMode, false, func(router *Engine) { router.LoadHTMLGlob("./testdata/template/*") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLGlobTestMode(t *testing.T) { ts := setupHTMLFiles( t, TestMode, false, func(router *Engine) { router.LoadHTMLGlob("./testdata/template/*") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLGlobReleaseMode(t *testing.T) { ts := setupHTMLFiles( t, ReleaseMode, false, func(router *Engine) { router.LoadHTMLGlob("./testdata/template/*") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLGlobUsingTLS(t *testing.T) { ts := setupHTMLFiles( t, DebugMode, true, func(router *Engine) { router.LoadHTMLGlob("./testdata/template/*") }, ) defer ts.Close() // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLGlobFromFuncMap(t *testing.T) { ts := setupHTMLFiles( t, DebugMode, false, func(router *Engine) { router.LoadHTMLGlob("./testdata/template/*") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) } func init() { SetMode(TestMode) } func TestCreateEngine(t *testing.T) { router := New() assert.Equal(t, "/", router.basePath) assert.Equal(t, router.engine, router) assert.Empty(t, router.Handlers) } func TestLoadHTMLFilesTestMode(t *testing.T) { ts := setupHTMLFiles( t, TestMode, false, func(router *Engine) { router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLFilesDebugMode(t *testing.T) { ts := setupHTMLFiles( t, DebugMode, false, func(router *Engine) { router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLFilesReleaseMode(t *testing.T) { ts := setupHTMLFiles( t, ReleaseMode, false, func(router *Engine) { router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLFilesUsingTLS(t *testing.T) { ts := setupHTMLFiles( t, TestMode, true, func(router *Engine) { router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") }, ) defer ts.Close() // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } func TestLoadHTMLFilesFuncMap(t *testing.T) { ts := setupHTMLFiles( t, TestMode, false, func(router *Engine) { router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") }, ) defer ts.Close() res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) } func TestAddRoute(t *testing.T) { router := New() router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) assert.NotNil(t, router.trees.get("GET")) assert.Nil(t, router.trees.get("POST")) router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) assert.NotNil(t, router.trees.get("GET")) assert.NotNil(t, router.trees.get("POST")) router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) } func TestAddRouteFails(t *testing.T) { router := New() assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) }) assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) }) assert.Panics(t, func() { router.addRoute("GET", "/", HandlersChain{}) }) router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) assert.Panics(t, func() { router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) }) } func TestCreateDefaultRouter(t *testing.T) { router := Default() assert.Len(t, router.Handlers, 2) } func TestNoRouteWithoutGlobalHandlers(t *testing.T) { var middleware0 HandlerFunc = func(c *Context) {} var middleware1 HandlerFunc = func(c *Context) {} router := New() router.NoRoute(middleware0) assert.Nil(t, router.Handlers) assert.Len(t, router.noRoute, 1) assert.Len(t, router.allNoRoute, 1) compareFunc(t, router.noRoute[0], middleware0) compareFunc(t, router.allNoRoute[0], middleware0) router.NoRoute(middleware1, middleware0) assert.Len(t, router.noRoute, 2) assert.Len(t, router.allNoRoute, 2) compareFunc(t, router.noRoute[0], middleware1) compareFunc(t, router.allNoRoute[0], middleware1) compareFunc(t, router.noRoute[1], middleware0) compareFunc(t, router.allNoRoute[1], middleware0) } func TestNoRouteWithGlobalHandlers(t *testing.T) { var middleware0 HandlerFunc = func(c *Context) {} var middleware1 HandlerFunc = func(c *Context) {} var middleware2 HandlerFunc = func(c *Context) {} router := New() router.Use(middleware2) router.NoRoute(middleware0) assert.Len(t, router.allNoRoute, 2) assert.Len(t, router.Handlers, 1) assert.Len(t, router.noRoute, 1) compareFunc(t, router.Handlers[0], middleware2) compareFunc(t, router.noRoute[0], middleware0) compareFunc(t, router.allNoRoute[0], middleware2) compareFunc(t, router.allNoRoute[1], middleware0) router.Use(middleware1) assert.Len(t, router.allNoRoute, 3) assert.Len(t, router.Handlers, 2) assert.Len(t, router.noRoute, 1) compareFunc(t, router.Handlers[0], middleware2) compareFunc(t, router.Handlers[1], middleware1) compareFunc(t, router.noRoute[0], middleware0) compareFunc(t, router.allNoRoute[0], middleware2) compareFunc(t, router.allNoRoute[1], middleware1) compareFunc(t, router.allNoRoute[2], middleware0) } func TestNoMethodWithoutGlobalHandlers(t *testing.T) { var middleware0 HandlerFunc = func(c *Context) {} var middleware1 HandlerFunc = func(c *Context) {} router := New() router.NoMethod(middleware0) assert.Empty(t, router.Handlers) assert.Len(t, router.noMethod, 1) assert.Len(t, router.allNoMethod, 1) compareFunc(t, router.noMethod[0], middleware0) compareFunc(t, router.allNoMethod[0], middleware0) router.NoMethod(middleware1, middleware0) assert.Len(t, router.noMethod, 2) assert.Len(t, router.allNoMethod, 2) compareFunc(t, router.noMethod[0], middleware1) compareFunc(t, router.allNoMethod[0], middleware1) compareFunc(t, router.noMethod[1], middleware0) compareFunc(t, router.allNoMethod[1], middleware0) } func TestRebuild404Handlers(t *testing.T) { } func TestNoMethodWithGlobalHandlers(t *testing.T) { var middleware0 HandlerFunc = func(c *Context) {} var middleware1 HandlerFunc = func(c *Context) {} var middleware2 HandlerFunc = func(c *Context) {} router := New() router.Use(middleware2) router.NoMethod(middleware0) assert.Len(t, router.allNoMethod, 2) assert.Len(t, router.Handlers, 1) assert.Len(t, router.noMethod, 1) compareFunc(t, router.Handlers[0], middleware2) compareFunc(t, router.noMethod[0], middleware0) compareFunc(t, router.allNoMethod[0], middleware2) compareFunc(t, router.allNoMethod[1], middleware0) router.Use(middleware1) assert.Len(t, router.allNoMethod, 3) assert.Len(t, router.Handlers, 2) assert.Len(t, router.noMethod, 1) compareFunc(t, router.Handlers[0], middleware2) compareFunc(t, router.Handlers[1], middleware1) compareFunc(t, router.noMethod[0], middleware0) compareFunc(t, router.allNoMethod[0], middleware2) compareFunc(t, router.allNoMethod[1], middleware1) compareFunc(t, router.allNoMethod[2], middleware0) } func compareFunc(t *testing.T, a, b interface{}) { sf1 := reflect.ValueOf(a) sf2 := reflect.ValueOf(b) if sf1.Pointer() != sf2.Pointer() { t.Error("different functions") } } func TestListOfRoutes(t *testing.T) { router := New() router.GET("/favicon.ico", handlerTest1) router.GET("/", handlerTest1) group := router.Group("/users") { group.GET("/", handlerTest2) group.GET("/:id", handlerTest1) group.POST("/:id", handlerTest2) } router.Static("/static", ".") list := router.Routes() assert.Len(t, list, 7) assertRoutePresent(t, list, RouteInfo{ Method: "GET", Path: "/favicon.ico", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ Method: "GET", Path: "/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ Method: "GET", Path: "/users/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) assertRoutePresent(t, list, RouteInfo{ Method: "GET", Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ Method: "POST", Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) } func TestEngineHandleContext(t *testing.T) { r := New() r.GET("/", func(c *Context) { c.Request.URL.Path = "/v2" r.HandleContext(c) }) v2 := r.Group("/v2") { v2.GET("/", func(c *Context) {}) } assert.NotPanics(t, func() { w := performRequest(r, "GET", "/") assert.Equal(t, 301, w.Code) }) } func TestEngineHandleContextManyReEntries(t *testing.T) { expectValue := 10000 var handlerCounter, middlewareCounter int64 r := New() r.Use(func(c *Context) { atomic.AddInt64(&middlewareCounter, 1) }) r.GET("/:count", func(c *Context) { countStr := c.Param("count") count, err := strconv.Atoi(countStr) assert.NoError(t, err) n, err := c.Writer.Write([]byte(".")) assert.NoError(t, err) assert.Equal(t, 1, n) switch { case count > 0: c.Request.URL.Path = "/" + strconv.Itoa(count-1) r.HandleContext(c) } }, func(c *Context) { atomic.AddInt64(&handlerCounter, 1) }) assert.NotPanics(t, func() { w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value assert.Equal(t, 200, w.Code) assert.Equal(t, expectValue, w.Body.Len()) }) assert.Equal(t, int64(expectValue), handlerCounter) assert.Equal(t, int64(expectValue), middlewareCounter) } func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { assert.Regexp(t, wantRoute.Handler, gotRoute.Handler) return } } t.Errorf("route not found: %v", wantRoute) } func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} gin-1.6.3/githubapi_test.go000066400000000000000000000416621365354716400156670ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "fmt" "math/rand" "net/http" "net/http/httptest" "os" "testing" "github.com/stretchr/testify/assert" ) type route struct { method string path string } // http://developer.github.com/v3/ var githubAPI = []route{ // OAuth Authorizations {http.MethodGet, "/authorizations"}, {http.MethodGet, "/authorizations/:id"}, {http.MethodPost, "/authorizations"}, //{http.MethodPut, "/authorizations/clients/:client_id"}, //{http.MethodPatch, "/authorizations/:id"}, {http.MethodDelete, "/authorizations/:id"}, {http.MethodGet, "/applications/:client_id/tokens/:access_token"}, {http.MethodDelete, "/applications/:client_id/tokens"}, {http.MethodDelete, "/applications/:client_id/tokens/:access_token"}, // Activity {http.MethodGet, "/events"}, {http.MethodGet, "/repos/:owner/:repo/events"}, {http.MethodGet, "/networks/:owner/:repo/events"}, {http.MethodGet, "/orgs/:org/events"}, {http.MethodGet, "/users/:user/received_events"}, {http.MethodGet, "/users/:user/received_events/public"}, {http.MethodGet, "/users/:user/events"}, {http.MethodGet, "/users/:user/events/public"}, {http.MethodGet, "/users/:user/events/orgs/:org"}, {http.MethodGet, "/feeds"}, {http.MethodGet, "/notifications"}, {http.MethodGet, "/repos/:owner/:repo/notifications"}, {http.MethodPut, "/notifications"}, {http.MethodPut, "/repos/:owner/:repo/notifications"}, {http.MethodGet, "/notifications/threads/:id"}, //{http.MethodPatch, "/notifications/threads/:id"}, {http.MethodGet, "/notifications/threads/:id/subscription"}, {http.MethodPut, "/notifications/threads/:id/subscription"}, {http.MethodDelete, "/notifications/threads/:id/subscription"}, {http.MethodGet, "/repos/:owner/:repo/stargazers"}, {http.MethodGet, "/users/:user/starred"}, {http.MethodGet, "/user/starred"}, {http.MethodGet, "/user/starred/:owner/:repo"}, {http.MethodPut, "/user/starred/:owner/:repo"}, {http.MethodDelete, "/user/starred/:owner/:repo"}, {http.MethodGet, "/repos/:owner/:repo/subscribers"}, {http.MethodGet, "/users/:user/subscriptions"}, {http.MethodGet, "/user/subscriptions"}, {http.MethodGet, "/repos/:owner/:repo/subscription"}, {http.MethodPut, "/repos/:owner/:repo/subscription"}, {http.MethodDelete, "/repos/:owner/:repo/subscription"}, {http.MethodGet, "/user/subscriptions/:owner/:repo"}, {http.MethodPut, "/user/subscriptions/:owner/:repo"}, {http.MethodDelete, "/user/subscriptions/:owner/:repo"}, // Gists {http.MethodGet, "/users/:user/gists"}, {http.MethodGet, "/gists"}, //{http.MethodGet, "/gists/public"}, //{http.MethodGet, "/gists/starred"}, {http.MethodGet, "/gists/:id"}, {http.MethodPost, "/gists"}, //{http.MethodPatch, "/gists/:id"}, {http.MethodPut, "/gists/:id/star"}, {http.MethodDelete, "/gists/:id/star"}, {http.MethodGet, "/gists/:id/star"}, {http.MethodPost, "/gists/:id/forks"}, {http.MethodDelete, "/gists/:id"}, // Git Data {http.MethodGet, "/repos/:owner/:repo/git/blobs/:sha"}, {http.MethodPost, "/repos/:owner/:repo/git/blobs"}, {http.MethodGet, "/repos/:owner/:repo/git/commits/:sha"}, {http.MethodPost, "/repos/:owner/:repo/git/commits"}, //{http.MethodGet, "/repos/:owner/:repo/git/refs/*ref"}, {http.MethodGet, "/repos/:owner/:repo/git/refs"}, {http.MethodPost, "/repos/:owner/:repo/git/refs"}, //{http.MethodPatch, "/repos/:owner/:repo/git/refs/*ref"}, //{http.MethodDelete, "/repos/:owner/:repo/git/refs/*ref"}, {http.MethodGet, "/repos/:owner/:repo/git/tags/:sha"}, {http.MethodPost, "/repos/:owner/:repo/git/tags"}, {http.MethodGet, "/repos/:owner/:repo/git/trees/:sha"}, {http.MethodPost, "/repos/:owner/:repo/git/trees"}, // Issues {http.MethodGet, "/issues"}, {http.MethodGet, "/user/issues"}, {http.MethodGet, "/orgs/:org/issues"}, {http.MethodGet, "/repos/:owner/:repo/issues"}, {http.MethodGet, "/repos/:owner/:repo/issues/:number"}, {http.MethodPost, "/repos/:owner/:repo/issues"}, //{http.MethodPatch, "/repos/:owner/:repo/issues/:number"}, {http.MethodGet, "/repos/:owner/:repo/assignees"}, {http.MethodGet, "/repos/:owner/:repo/assignees/:assignee"}, {http.MethodGet, "/repos/:owner/:repo/issues/:number/comments"}, //{http.MethodGet, "/repos/:owner/:repo/issues/comments"}, //{http.MethodGet, "/repos/:owner/:repo/issues/comments/:id"}, {http.MethodPost, "/repos/:owner/:repo/issues/:number/comments"}, //{http.MethodPatch, "/repos/:owner/:repo/issues/comments/:id"}, //{http.MethodDelete, "/repos/:owner/:repo/issues/comments/:id"}, {http.MethodGet, "/repos/:owner/:repo/issues/:number/events"}, //{http.MethodGet, "/repos/:owner/:repo/issues/events"}, //{http.MethodGet, "/repos/:owner/:repo/issues/events/:id"}, {http.MethodGet, "/repos/:owner/:repo/labels"}, {http.MethodGet, "/repos/:owner/:repo/labels/:name"}, {http.MethodPost, "/repos/:owner/:repo/labels"}, //{http.MethodPatch, "/repos/:owner/:repo/labels/:name"}, {http.MethodDelete, "/repos/:owner/:repo/labels/:name"}, {http.MethodGet, "/repos/:owner/:repo/issues/:number/labels"}, {http.MethodPost, "/repos/:owner/:repo/issues/:number/labels"}, {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels/:name"}, {http.MethodPut, "/repos/:owner/:repo/issues/:number/labels"}, {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels"}, {http.MethodGet, "/repos/:owner/:repo/milestones/:number/labels"}, {http.MethodGet, "/repos/:owner/:repo/milestones"}, {http.MethodGet, "/repos/:owner/:repo/milestones/:number"}, {http.MethodPost, "/repos/:owner/:repo/milestones"}, //{http.MethodPatch, "/repos/:owner/:repo/milestones/:number"}, {http.MethodDelete, "/repos/:owner/:repo/milestones/:number"}, // Miscellaneous {http.MethodGet, "/emojis"}, {http.MethodGet, "/gitignore/templates"}, {http.MethodGet, "/gitignore/templates/:name"}, {http.MethodPost, "/markdown"}, {http.MethodPost, "/markdown/raw"}, {http.MethodGet, "/meta"}, {http.MethodGet, "/rate_limit"}, // Organizations {http.MethodGet, "/users/:user/orgs"}, {http.MethodGet, "/user/orgs"}, {http.MethodGet, "/orgs/:org"}, //{http.MethodPatch, "/orgs/:org"}, {http.MethodGet, "/orgs/:org/members"}, {http.MethodGet, "/orgs/:org/members/:user"}, {http.MethodDelete, "/orgs/:org/members/:user"}, {http.MethodGet, "/orgs/:org/public_members"}, {http.MethodGet, "/orgs/:org/public_members/:user"}, {http.MethodPut, "/orgs/:org/public_members/:user"}, {http.MethodDelete, "/orgs/:org/public_members/:user"}, {http.MethodGet, "/orgs/:org/teams"}, {http.MethodGet, "/teams/:id"}, {http.MethodPost, "/orgs/:org/teams"}, //{http.MethodPatch, "/teams/:id"}, {http.MethodDelete, "/teams/:id"}, {http.MethodGet, "/teams/:id/members"}, {http.MethodGet, "/teams/:id/members/:user"}, {http.MethodPut, "/teams/:id/members/:user"}, {http.MethodDelete, "/teams/:id/members/:user"}, {http.MethodGet, "/teams/:id/repos"}, {http.MethodGet, "/teams/:id/repos/:owner/:repo"}, {http.MethodPut, "/teams/:id/repos/:owner/:repo"}, {http.MethodDelete, "/teams/:id/repos/:owner/:repo"}, {http.MethodGet, "/user/teams"}, // Pull Requests {http.MethodGet, "/repos/:owner/:repo/pulls"}, {http.MethodGet, "/repos/:owner/:repo/pulls/:number"}, {http.MethodPost, "/repos/:owner/:repo/pulls"}, //{http.MethodPatch, "/repos/:owner/:repo/pulls/:number"}, {http.MethodGet, "/repos/:owner/:repo/pulls/:number/commits"}, {http.MethodGet, "/repos/:owner/:repo/pulls/:number/files"}, {http.MethodGet, "/repos/:owner/:repo/pulls/:number/merge"}, {http.MethodPut, "/repos/:owner/:repo/pulls/:number/merge"}, {http.MethodGet, "/repos/:owner/:repo/pulls/:number/comments"}, //{http.MethodGet, "/repos/:owner/:repo/pulls/comments"}, //{http.MethodGet, "/repos/:owner/:repo/pulls/comments/:number"}, {http.MethodPut, "/repos/:owner/:repo/pulls/:number/comments"}, //{http.MethodPatch, "/repos/:owner/:repo/pulls/comments/:number"}, //{http.MethodDelete, "/repos/:owner/:repo/pulls/comments/:number"}, // Repositories {http.MethodGet, "/user/repos"}, {http.MethodGet, "/users/:user/repos"}, {http.MethodGet, "/orgs/:org/repos"}, {http.MethodGet, "/repositories"}, {http.MethodPost, "/user/repos"}, {http.MethodPost, "/orgs/:org/repos"}, {http.MethodGet, "/repos/:owner/:repo"}, //{http.MethodPatch, "/repos/:owner/:repo"}, {http.MethodGet, "/repos/:owner/:repo/contributors"}, {http.MethodGet, "/repos/:owner/:repo/languages"}, {http.MethodGet, "/repos/:owner/:repo/teams"}, {http.MethodGet, "/repos/:owner/:repo/tags"}, {http.MethodGet, "/repos/:owner/:repo/branches"}, {http.MethodGet, "/repos/:owner/:repo/branches/:branch"}, {http.MethodDelete, "/repos/:owner/:repo"}, {http.MethodGet, "/repos/:owner/:repo/collaborators"}, {http.MethodGet, "/repos/:owner/:repo/collaborators/:user"}, {http.MethodPut, "/repos/:owner/:repo/collaborators/:user"}, {http.MethodDelete, "/repos/:owner/:repo/collaborators/:user"}, {http.MethodGet, "/repos/:owner/:repo/comments"}, {http.MethodGet, "/repos/:owner/:repo/commits/:sha/comments"}, {http.MethodPost, "/repos/:owner/:repo/commits/:sha/comments"}, {http.MethodGet, "/repos/:owner/:repo/comments/:id"}, //{http.MethodPatch, "/repos/:owner/:repo/comments/:id"}, {http.MethodDelete, "/repos/:owner/:repo/comments/:id"}, {http.MethodGet, "/repos/:owner/:repo/commits"}, {http.MethodGet, "/repos/:owner/:repo/commits/:sha"}, {http.MethodGet, "/repos/:owner/:repo/readme"}, //{http.MethodGet, "/repos/:owner/:repo/contents/*path"}, //{http.MethodPut, "/repos/:owner/:repo/contents/*path"}, //{http.MethodDelete, "/repos/:owner/:repo/contents/*path"}, //{http.MethodGet, "/repos/:owner/:repo/:archive_format/:ref"}, {http.MethodGet, "/repos/:owner/:repo/keys"}, {http.MethodGet, "/repos/:owner/:repo/keys/:id"}, {http.MethodPost, "/repos/:owner/:repo/keys"}, //{http.MethodPatch, "/repos/:owner/:repo/keys/:id"}, {http.MethodDelete, "/repos/:owner/:repo/keys/:id"}, {http.MethodGet, "/repos/:owner/:repo/downloads"}, {http.MethodGet, "/repos/:owner/:repo/downloads/:id"}, {http.MethodDelete, "/repos/:owner/:repo/downloads/:id"}, {http.MethodGet, "/repos/:owner/:repo/forks"}, {http.MethodPost, "/repos/:owner/:repo/forks"}, {http.MethodGet, "/repos/:owner/:repo/hooks"}, {http.MethodGet, "/repos/:owner/:repo/hooks/:id"}, {http.MethodPost, "/repos/:owner/:repo/hooks"}, //{http.MethodPatch, "/repos/:owner/:repo/hooks/:id"}, {http.MethodPost, "/repos/:owner/:repo/hooks/:id/tests"}, {http.MethodDelete, "/repos/:owner/:repo/hooks/:id"}, {http.MethodPost, "/repos/:owner/:repo/merges"}, {http.MethodGet, "/repos/:owner/:repo/releases"}, {http.MethodGet, "/repos/:owner/:repo/releases/:id"}, {http.MethodPost, "/repos/:owner/:repo/releases"}, //{http.MethodPatch, "/repos/:owner/:repo/releases/:id"}, {http.MethodDelete, "/repos/:owner/:repo/releases/:id"}, {http.MethodGet, "/repos/:owner/:repo/releases/:id/assets"}, {http.MethodGet, "/repos/:owner/:repo/stats/contributors"}, {http.MethodGet, "/repos/:owner/:repo/stats/commit_activity"}, {http.MethodGet, "/repos/:owner/:repo/stats/code_frequency"}, {http.MethodGet, "/repos/:owner/:repo/stats/participation"}, {http.MethodGet, "/repos/:owner/:repo/stats/punch_card"}, {http.MethodGet, "/repos/:owner/:repo/statuses/:ref"}, {http.MethodPost, "/repos/:owner/:repo/statuses/:ref"}, // Search {http.MethodGet, "/search/repositories"}, {http.MethodGet, "/search/code"}, {http.MethodGet, "/search/issues"}, {http.MethodGet, "/search/users"}, {http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword"}, {http.MethodGet, "/legacy/repos/search/:keyword"}, {http.MethodGet, "/legacy/user/search/:keyword"}, {http.MethodGet, "/legacy/user/email/:email"}, // Users {http.MethodGet, "/users/:user"}, {http.MethodGet, "/user"}, //{http.MethodPatch, "/user"}, {http.MethodGet, "/users"}, {http.MethodGet, "/user/emails"}, {http.MethodPost, "/user/emails"}, {http.MethodDelete, "/user/emails"}, {http.MethodGet, "/users/:user/followers"}, {http.MethodGet, "/user/followers"}, {http.MethodGet, "/users/:user/following"}, {http.MethodGet, "/user/following"}, {http.MethodGet, "/user/following/:user"}, {http.MethodGet, "/users/:user/following/:target_user"}, {http.MethodPut, "/user/following/:user"}, {http.MethodDelete, "/user/following/:user"}, {http.MethodGet, "/users/:user/keys"}, {http.MethodGet, "/user/keys"}, {http.MethodGet, "/user/keys/:id"}, {http.MethodPost, "/user/keys"}, //{http.MethodPatch, "/user/keys/:id"}, {http.MethodDelete, "/user/keys/:id"}, } func TestShouldBindUri(t *testing.T) { DefaultWriter = os.Stdout router := New() type Person struct { Name string `uri:"name" binding:"required"` Id string `uri:"id" binding:"required"` } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.ShouldBindUri(&person)) assert.True(t, "" != person.Name) assert.True(t, "" != person.Id) c.String(http.StatusOK, "ShouldBindUri test OK") }) path, _ := exampleFromPath("/rest/:name/:id") w := performRequest(router, http.MethodGet, path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } func TestBindUri(t *testing.T) { DefaultWriter = os.Stdout router := New() type Person struct { Name string `uri:"name" binding:"required"` Id string `uri:"id" binding:"required"` } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.BindUri(&person)) assert.True(t, "" != person.Name) assert.True(t, "" != person.Id) c.String(http.StatusOK, "BindUri test OK") }) path, _ := exampleFromPath("/rest/:name/:id") w := performRequest(router, http.MethodGet, path) assert.Equal(t, "BindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } func TestBindUriError(t *testing.T) { DefaultWriter = os.Stdout router := New() type Member struct { Number string `uri:"num" binding:"required,uuid"` } router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) { var m Member assert.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") w1 := performRequest(router, http.MethodGet, path1) assert.Equal(t, http.StatusBadRequest, w1.Code) } func TestRaceContextCopy(t *testing.T) { DefaultWriter = os.Stdout router := Default() router.GET("/test/copy/race", func(c *Context) { c.Set("1", 0) c.Set("2", 0) // Sending a copy of the Context to two separate routines go readWriteKeys(c.Copy()) go readWriteKeys(c.Copy()) c.String(http.StatusOK, "run OK, no panics") }) w := performRequest(router, http.MethodGet, "/test/copy/race") assert.Equal(t, "run OK, no panics", w.Body.String()) } func readWriteKeys(c *Context) { for { c.Set("1", rand.Int()) c.Set("2", c.Value("1")) } } func githubConfigRouter(router *Engine) { for _, route := range githubAPI { router.Handle(route.method, route.path, func(c *Context) { output := make(map[string]string, len(c.Params)+1) output["status"] = "good" for _, param := range c.Params { output[param.Key] = param.Value } c.JSON(http.StatusOK, output) }) } } func TestGithubAPI(t *testing.T) { DefaultWriter = os.Stdout router := New() githubConfigRouter(router) for _, route := range githubAPI { path, values := exampleFromPath(route.path) w := performRequest(router, route.method, path) // TEST assert.Contains(t, w.Body.String(), "\"status\":\"good\"") for _, value := range values { str := fmt.Sprintf("\"%s\":\"%s\"", value.Key, value.Value) assert.Contains(t, w.Body.String(), str) } } } func exampleFromPath(path string) (string, Params) { output := new(bytes.Buffer) params := make(Params, 0, 6) start := -1 for i, c := range path { if c == ':' { start = i + 1 } if start >= 0 { if c == '/' { value := fmt.Sprint(rand.Intn(100000)) params = append(params, Param{ Key: path[start:i], Value: value, }) output.WriteString(value) output.WriteRune(c) start = -1 } } else { output.WriteRune(c) } } if start >= 0 { value := fmt.Sprint(rand.Intn(100000)) params = append(params, Param{ Key: path[start:], Value: value, }) output.WriteString(value) } return output.String(), params } func BenchmarkGithub(b *testing.B) { router := New() githubConfigRouter(router) runRequest(b, router, http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword") } func BenchmarkParallelGithub(b *testing.B) { DefaultWriter = os.Stdout router := New() githubConfigRouter(router) req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil) b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. for pb.Next() { w := httptest.NewRecorder() router.ServeHTTP(w, req) } }) } func BenchmarkParallelGithubDefault(b *testing.B) { DefaultWriter = os.Stdout router := New() githubConfigRouter(router) req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil) b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. for pb.Next() { w := httptest.NewRecorder() router.ServeHTTP(w, req) } }) } gin-1.6.3/go.mod000066400000000000000000000005241365354716400134230ustar00rootroot00000000000000module github.com/gin-gonic/gin go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.2.0 github.com/golang/protobuf v1.3.3 github.com/json-iterator/go v1.1.9 github.com/mattn/go-isatty v0.0.12 github.com/stretchr/testify v1.4.0 github.com/ugorji/go/codec v1.1.7 gopkg.in/yaml.v2 v2.2.8 ) gin-1.6.3/go.sum000066400000000000000000000100621365354716400134460ustar00rootroot00000000000000github.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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gin-1.6.3/internal/000077500000000000000000000000001365354716400141305ustar00rootroot00000000000000gin-1.6.3/internal/bytesconv/000077500000000000000000000000001365354716400161445ustar00rootroot00000000000000gin-1.6.3/internal/bytesconv/bytesconv.go000066400000000000000000000007531365354716400205140ustar00rootroot00000000000000package bytesconv import ( "reflect" "unsafe" ) // StringToBytes converts string to byte slice without a memory allocation. func StringToBytes(s string) (b []byte) { sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len return b } // BytesToString converts byte slice to string without a memory allocation. func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } gin-1.6.3/internal/bytesconv/bytesconv_test.go000066400000000000000000000041071365354716400215500ustar00rootroot00000000000000package bytesconv import ( "bytes" "math/rand" "strings" "testing" "time" ) var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." var testBytes = []byte(testString) func rawBytesToStr(b []byte) string { return string(b) } func rawStrToBytes(s string) []byte { return []byte(s) } // go test -v func TestBytesToString(t *testing.T) { data := make([]byte, 1024) for i := 0; i < 100; i++ { rand.Read(data) if rawBytesToStr(data) != BytesToString(data) { t.Fatal("don't match") } } } const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { sb.WriteByte(letterBytes[idx]) i-- } cache >>= letterIdxBits remain-- } return sb.String() } func TestStringToBytes(t *testing.T) { for i := 0; i < 100; i++ { s := RandStringBytesMaskImprSrcSB(64) if !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) { t.Fatal("don't match") } } } // go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { for i := 0; i < b.N; i++ { rawBytesToStr(testBytes) } } func BenchmarkBytesConvBytesToStr(b *testing.B) { for i := 0; i < b.N; i++ { BytesToString(testBytes) } } func BenchmarkBytesConvStrToBytesRaw(b *testing.B) { for i := 0; i < b.N; i++ { rawStrToBytes(testString) } } func BenchmarkBytesConvStrToBytes(b *testing.B) { for i := 0; i < b.N; i++ { StringToBytes(testString) } } gin-1.6.3/internal/json/000077500000000000000000000000001365354716400151015ustar00rootroot00000000000000gin-1.6.3/internal/json/json.go000066400000000000000000000011411365354716400163760ustar00rootroot00000000000000// Copyright 2017 Bo-Yi Wu. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !jsoniter package json import "encoding/json" var ( // Marshal is exported by gin/json package. Marshal = json.Marshal // Unmarshal is exported by gin/json package. Unmarshal = json.Unmarshal // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent // NewDecoder is exported by gin/json package. NewDecoder = json.NewDecoder // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) gin-1.6.3/internal/json/jsoniter.go000066400000000000000000000012431365354716400172650ustar00rootroot00000000000000// Copyright 2017 Bo-Yi Wu. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build jsoniter package json import "github.com/json-iterator/go" var ( json = jsoniter.ConfigCompatibleWithStandardLibrary // Marshal is exported by gin/json package. Marshal = json.Marshal // Unmarshal is exported by gin/json package. Unmarshal = json.Unmarshal // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent // NewDecoder is exported by gin/json package. NewDecoder = json.NewDecoder // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) gin-1.6.3/logger.go000066400000000000000000000152741365354716400141330ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "io" "net/http" "os" "time" "github.com/mattn/go-isatty" ) type consoleColorModeValue int const ( autoColor consoleColorModeValue = iota disableColor forceColor ) const ( green = "\033[97;42m" white = "\033[90;47m" yellow = "\033[90;43m" red = "\033[97;41m" blue = "\033[97;44m" magenta = "\033[97;45m" cyan = "\033[97;46m" reset = "\033[0m" ) var consoleColorMode = autoColor // LoggerConfig defines the config for Logger middleware. type LoggerConfig struct { // Optional. Default value is gin.defaultLogFormatter Formatter LogFormatter // Output is a writer where logs are written. // Optional. Default value is gin.DefaultWriter. Output io.Writer // SkipPaths is a url path array which logs are not written. // Optional. SkipPaths []string } // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter type LogFormatter func(params LogFormatterParams) string // LogFormatterParams is the structure any formatter will be handed when time to log comes type LogFormatterParams struct { Request *http.Request // TimeStamp shows the time after the server returns a response. TimeStamp time.Time // StatusCode is HTTP response code. StatusCode int // Latency is how much time the server cost to process a certain request. Latency time.Duration // ClientIP equals Context's ClientIP method. ClientIP string // Method is the HTTP method given to the request. Method string // Path is a path the client requests. Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string // isTerm shows whether does gin's output descriptor refers to a terminal. isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. Keys map[string]interface{} } // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. func (p *LogFormatterParams) StatusCodeColor() string { code := p.StatusCode switch { case code >= http.StatusOK && code < http.StatusMultipleChoices: return green case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: return white case code >= http.StatusBadRequest && code < http.StatusInternalServerError: return yellow default: return red } } // MethodColor is the ANSI color for appropriately logging http method to a terminal. func (p *LogFormatterParams) MethodColor() string { method := p.Method switch method { case http.MethodGet: return blue case http.MethodPost: return cyan case http.MethodPut: return yellow case http.MethodDelete: return red case http.MethodPatch: return green case http.MethodHead: return magenta case http.MethodOptions: return white default: return reset } } // ResetColor resets all escape attributes. func (p *LogFormatterParams) ResetColor() string { return reset } // IsOutputColor indicates whether can colors be outputted to the log. func (p *LogFormatterParams) IsOutputColor() bool { return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) } // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() } if param.Latency > time.Minute { // Truncate in a golang < 1.8 safe way param.Latency = param.Latency - param.Latency%time.Second } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, param.Latency, param.ClientIP, methodColor, param.Method, resetColor, param.Path, param.ErrorMessage, ) } // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { consoleColorMode = forceColor } // ErrorLogger returns a handlerfunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } // ErrorLoggerT returns a handlerfunc for a given error type. func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() errors := c.Errors.ByType(typ) if len(errors) > 0 { c.JSON(-1, errors) } } } // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. // By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { return LoggerWithConfig(LoggerConfig{}) } // LoggerWithFormatter instance a Logger middleware with the specified log format function. func LoggerWithFormatter(f LogFormatter) HandlerFunc { return LoggerWithConfig(LoggerConfig{ Formatter: f, }) } // LoggerWithWriter instance a Logger middleware with the specified writer buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { return LoggerWithConfig(LoggerConfig{ Output: out, SkipPaths: notlogged, }) } // LoggerWithConfig instance a Logger middleware with config. func LoggerWithConfig(conf LoggerConfig) HandlerFunc { formatter := conf.Formatter if formatter == nil { formatter = defaultLogFormatter } out := conf.Output if out == nil { out = DefaultWriter } notlogged := conf.SkipPaths isTerm := true if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false } var skip map[string]struct{} if length := len(notlogged); length > 0 { skip = make(map[string]struct{}, length) for _, path := range notlogged { skip[path] = struct{}{} } } return func(c *Context) { // Start timer start := time.Now() path := c.Request.URL.Path raw := c.Request.URL.RawQuery // Process request c.Next() // Log only when path is not being skipped if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, isTerm: isTerm, Keys: c.Keys, } // Stop timer param.TimeStamp = time.Now() param.Latency = param.TimeStamp.Sub(start) param.ClientIP = c.ClientIP() param.Method = c.Request.Method param.StatusCode = c.Writer.Status() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() param.BodySize = c.Writer.Size() if raw != "" { path = path + "?" + raw } param.Path = path fmt.Fprint(out, formatter(param)) } } } gin-1.6.3/logger_test.go000066400000000000000000000323001365354716400151570ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "errors" "fmt" "net/http" "testing" "time" "github.com/stretchr/testify/assert" ) func init() { SetMode(TestMode) } func TestLogger(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithWriter(buffer)) router.GET("/example", func(c *Context) {}) router.POST("/example", func(c *Context) {}) router.PUT("/example", func(c *Context) {}) router.DELETE("/example", func(c *Context) {}) router.PATCH("/example", func(c *Context) {}) router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) performRequest(router, "GET", "/example?a=100") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() performRequest(router, "POST", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "PUT", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "DELETE", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "PATCH", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "HEAD", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "OPTIONS", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "GET", "/notfound") assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") } func TestLoggerWithConfig(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) router.GET("/example", func(c *Context) {}) router.POST("/example", func(c *Context) {}) router.PUT("/example", func(c *Context) {}) router.DELETE("/example", func(c *Context) {}) router.PATCH("/example", func(c *Context) {}) router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) performRequest(router, "GET", "/example?a=100") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() performRequest(router, "POST", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "PUT", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "DELETE", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "PATCH", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "HEAD", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "OPTIONS", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "/example") buffer.Reset() performRequest(router, "GET", "/notfound") assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") } func TestLoggerWithFormatter(t *testing.T) { buffer := new(bytes.Buffer) d := DefaultWriter DefaultWriter = buffer defer func() { DefaultWriter = d }() router := New() router.Use(LoggerWithFormatter(func(param LogFormatterParams) string { return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.StatusCode, param.Latency, param.ClientIP, param.Method, param.Path, param.ErrorMessage, ) })) router.GET("/example", func(c *Context) {}) performRequest(router, "GET", "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") } func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams var gotKeys map[string]interface{} buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{ Output: buffer, Formatter: func(param LogFormatterParams) string { // for assert test gotParam = param return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.StatusCode, param.Latency, param.ClientIP, param.Method, param.Path, param.ErrorMessage, ) }, })) router.GET("/example", func(c *Context) { // set dummy ClientIP c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") gotKeys = c.Keys }) performRequest(router, "GET", "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") // LogFormatterParams test assert.NotNil(t, gotParam.Request) assert.NotEmpty(t, gotParam.TimeStamp) assert.Equal(t, 200, gotParam.StatusCode) assert.NotEmpty(t, gotParam.Latency) assert.Equal(t, "20.20.20.20", gotParam.ClientIP) assert.Equal(t, "GET", gotParam.Method) assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) } func TestDefaultLogFormatter(t *testing.T) { timeStamp := time.Unix(1544173902, 0).UTC() termFalseParam := LogFormatterParams{ TimeStamp: timeStamp, StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", Method: "GET", Path: "/", ErrorMessage: "", isTerm: false, } termTrueParam := LogFormatterParams{ TimeStamp: timeStamp, StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", Method: "GET", Path: "/", ErrorMessage: "", isTerm: true, } termTrueLongDurationParam := LogFormatterParams{ TimeStamp: timeStamp, StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", Method: "GET", Path: "/", ErrorMessage: "", isTerm: true, } termFalseLongDurationParam := LogFormatterParams{ TimeStamp: timeStamp, StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", Method: "GET", Path: "/", ErrorMessage: "", isTerm: false, } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) } func TestColorForMethod(t *testing.T) { colorForMethod := func(method string) string { p := LogFormatterParams{ Method: method, } return p.MethodColor() } assert.Equal(t, blue, colorForMethod("GET"), "get should be blue") assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan") assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow") assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red") assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green") assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta") assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white") assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color") } func TestColorForStatus(t *testing.T) { colorForStatus := func(code int) string { p := LogFormatterParams{ StatusCode: code, } return p.StatusCodeColor() } assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, red, colorForStatus(2), "other things should be red") } func TestResetColor(t *testing.T) { p := LogFormatterParams{} assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) } func TestIsOutputColor(t *testing.T) { // test with isTerm flag true. p := LogFormatterParams{ isTerm: true, } consoleColorMode = autoColor assert.Equal(t, true, p.IsOutputColor()) ForceConsoleColor() assert.Equal(t, true, p.IsOutputColor()) DisableConsoleColor() assert.Equal(t, false, p.IsOutputColor()) // test with isTerm flag false. p = LogFormatterParams{ isTerm: false, } consoleColorMode = autoColor assert.Equal(t, false, p.IsOutputColor()) ForceConsoleColor() assert.Equal(t, true, p.IsOutputColor()) DisableConsoleColor() assert.Equal(t, false, p.IsOutputColor()) // reset console color mode. consoleColorMode = autoColor } func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { c.Error(errors.New("this is an error")) // nolint: errcheck }) router.GET("/abort", func(c *Context) { c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck }) router.GET("/print", func(c *Context) { c.Error(errors.New("this is an error")) // nolint: errcheck c.String(http.StatusInternalServerError, "hola!") }) w := performRequest(router, "GET", "/error") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } func TestLoggerWithWriterSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) performRequest(router, "GET", "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() performRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } func TestLoggerWithConfigSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{ Output: buffer, SkipPaths: []string{"/skipped"}, })) router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) performRequest(router, "GET", "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() performRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } func TestDisableConsoleColor(t *testing.T) { New() assert.Equal(t, autoColor, consoleColorMode) DisableConsoleColor() assert.Equal(t, disableColor, consoleColorMode) // reset console color mode. consoleColorMode = autoColor } func TestForceConsoleColor(t *testing.T) { New() assert.Equal(t, autoColor, consoleColorMode) ForceConsoleColor() assert.Equal(t, forceColor, consoleColorMode) // reset console color mode. consoleColorMode = autoColor } gin-1.6.3/middleware_test.go000066400000000000000000000122251365354716400160210ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "errors" "net/http" "strings" "testing" "github.com/gin-contrib/sse" "github.com/stretchr/testify/assert" ) func TestMiddlewareGeneralCase(t *testing.T) { signature := "" router := New() router.Use(func(c *Context) { signature += "A" c.Next() signature += "B" }) router.Use(func(c *Context) { signature += "C" }) router.GET("/", func(c *Context) { signature += "D" }) router.NoRoute(func(c *Context) { signature += " X " }) router.NoMethod(func(c *Context) { signature += " XX " }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "ACDB", signature) } func TestMiddlewareNoRoute(t *testing.T) { signature := "" router := New() router.Use(func(c *Context) { signature += "A" c.Next() signature += "B" }) router.Use(func(c *Context) { signature += "C" c.Next() c.Next() c.Next() c.Next() signature += "D" }) router.NoRoute(func(c *Context) { signature += "E" c.Next() signature += "F" }, func(c *Context) { signature += "G" c.Next() signature += "H" }) router.NoMethod(func(c *Context) { signature += " X " }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "ACEGHFDB", signature) } func TestMiddlewareNoMethodEnabled(t *testing.T) { signature := "" router := New() router.HandleMethodNotAllowed = true router.Use(func(c *Context) { signature += "A" c.Next() signature += "B" }) router.Use(func(c *Context) { signature += "C" c.Next() signature += "D" }) router.NoMethod(func(c *Context) { signature += "E" c.Next() signature += "F" }, func(c *Context) { signature += "G" c.Next() signature += "H" }) router.NoRoute(func(c *Context) { signature += " X " }) router.POST("/", func(c *Context) { signature += " XX " }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, "ACEGHFDB", signature) } func TestMiddlewareNoMethodDisabled(t *testing.T) { signature := "" router := New() router.HandleMethodNotAllowed = false router.Use(func(c *Context) { signature += "A" c.Next() signature += "B" }) router.Use(func(c *Context) { signature += "C" c.Next() signature += "D" }) router.NoMethod(func(c *Context) { signature += "E" c.Next() signature += "F" }, func(c *Context) { signature += "G" c.Next() signature += "H" }) router.NoRoute(func(c *Context) { signature += " X " }) router.POST("/", func(c *Context) { signature += " XX " }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "AC X DB", signature) } func TestMiddlewareAbort(t *testing.T) { signature := "" router := New() router.Use(func(c *Context) { signature += "A" }) router.Use(func(c *Context) { signature += "C" c.AbortWithStatus(http.StatusUnauthorized) c.Next() signature += "D" }) router.GET("/", func(c *Context) { signature += " X " c.Next() signature += " XX " }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "ACD", signature) } func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { signature := "" router := New() router.Use(func(c *Context) { signature += "A" c.Next() c.AbortWithStatus(http.StatusGone) signature += "B" }) router.GET("/", func(c *Context) { signature += "C" c.Next() }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusGone, w.Code) assert.Equal(t, "ACB", signature) } // TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as // as well as Abort func TestMiddlewareFailHandlersChain(t *testing.T) { // SETUP signature := "" router := New() router.Use(func(context *Context) { signature += "A" context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck }) router.Use(func(context *Context) { signature += "B" context.Next() signature += "C" }) // RUN w := performRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "A", signature) } func TestMiddlewareWrite(t *testing.T) { router := New() router.Use(func(c *Context) { c.String(http.StatusBadRequest, "hola\n") }) router.Use(func(c *Context) { c.XML(http.StatusBadRequest, H{"foo": "bar"}) }) router.Use(func(c *Context) { c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }) router.GET("/", func(c *Context) { c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }, func(c *Context) { c.Render(http.StatusBadRequest, sse.Event{ Event: "test", Data: "message", }) }) w := performRequest(router, "GET", "/") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } gin-1.6.3/mode.go000066400000000000000000000042751365354716400135770ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "io" "os" "github.com/gin-gonic/gin/binding" ) // EnvGinMode indicates environment name for gin mode. const EnvGinMode = "GIN_MODE" const ( // DebugMode indicates gin mode is debug. DebugMode = "debug" // ReleaseMode indicates gin mode is release. ReleaseMode = "release" // TestMode indicates gin mode is test. TestMode = "test" ) const ( debugCode = iota releaseCode testCode ) // DefaultWriter is the default io.Writer used by Gin for debug output and // middleware output like Logger() or Recovery(). // Note that both Logger and Recovery provides custom ways to configure their // output io.Writer. // To support coloring in Windows use: // import "github.com/mattn/go-colorable" // gin.DefaultWriter = colorable.NewColorableStdout() var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr var ginMode = debugCode var modeName = DebugMode func init() { mode := os.Getenv(EnvGinMode) SetMode(mode) } // SetMode sets gin mode according to input string. func SetMode(value string) { switch value { case DebugMode, "": ginMode = debugCode case ReleaseMode: ginMode = releaseCode case TestMode: ginMode = testCode default: panic("gin mode unknown: " + value) } if value == "" { value = DebugMode } modeName = value } // DisableBindValidation closes the default validator. func DisableBindValidation() { binding.Validator = nil } // EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to // call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } // EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to // call the DisallowUnknownFields method on the JSON Decoder instance. func EnableJsonDecoderDisallowUnknownFields() { binding.EnableDecoderDisallowUnknownFields = true } // Mode returns currently gin mode. func Mode() string { return modeName } gin-1.6.3/mode_test.go000066400000000000000000000027251365354716400146340ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "os" "testing" "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" ) func init() { os.Setenv(EnvGinMode, TestMode) } func TestSetMode(t *testing.T) { assert.Equal(t, testCode, ginMode) assert.Equal(t, TestMode, Mode()) os.Unsetenv(EnvGinMode) SetMode("") assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) SetMode(DebugMode) assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) SetMode(ReleaseMode) assert.Equal(t, releaseCode, ginMode) assert.Equal(t, ReleaseMode, Mode()) SetMode(TestMode) assert.Equal(t, testCode, ginMode) assert.Equal(t, TestMode, Mode()) assert.Panics(t, func() { SetMode("unknown") }) } func TestDisableBindValidation(t *testing.T) { v := binding.Validator assert.NotNil(t, binding.Validator) DisableBindValidation() assert.Nil(t, binding.Validator) binding.Validator = v } func TestEnableJsonDecoderUseNumber(t *testing.T) { assert.False(t, binding.EnableDecoderUseNumber) EnableJsonDecoderUseNumber() assert.True(t, binding.EnableDecoderUseNumber) } func TestEnableJsonDecoderDisallowUnknownFields(t *testing.T) { assert.False(t, binding.EnableDecoderDisallowUnknownFields) EnableJsonDecoderDisallowUnknownFields() assert.True(t, binding.EnableDecoderDisallowUnknownFields) } gin-1.6.3/path.go000066400000000000000000000071251365354716400136040ustar00rootroot00000000000000// Copyright 2013 Julien Schmidt. All rights reserved. // Based on the path package, Copyright 2009 The Go Authors. // Use of this source code is governed by a BSD-style license that can be found // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE. package gin // cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // // The following rules are applied iteratively until no further processing can // be done: // 1. Replace multiple slashes with a single slash. // 2. Eliminate each . path name element (the current directory). // 3. Eliminate each inner .. path name element (the parent directory) // along with the non-.. element that precedes it. // 4. Eliminate .. elements that begin a rooted path: // that is, replace "/.." by "/" at the beginning of a path. // // If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { const stackBufSize = 128 // Turn empty string into "/" if p == "" { return "/" } // Reasonably sized buffer on stack to avoid allocations in the common case. // If a larger buffer is required, it gets allocated dynamically. buf := make([]byte, 0, stackBufSize) n := len(p) // Invariants: // reading from path; r is index of next byte to process. // writing to buf; w is index of next byte to write. // path must start with '/' r := 1 w := 1 if p[0] != '/' { r = 0 if n+1 > stackBufSize { buf = make([]byte, n+1) } else { buf = buf[:n+1] } buf[0] = '/' } trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop // gets completely inlined (bufApp calls). // loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function // calls (except make, if needed). for r < n { switch { case p[r] == '/': // empty path element, trailing slash is added after the end r++ case p[r] == '.' && r+1 == n: trailing = true r++ case p[r] == '.' && p[r+1] == '/': // . element r += 2 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): // .. element: remove to last / r += 3 if w > 1 { // can backtrack w-- if len(buf) == 0 { for w > 1 && p[w] != '/' { w-- } } else { for w > 1 && buf[w] != '/' { w-- } } } default: // Real path element. // Add slash if needed if w > 1 { bufApp(&buf, p, w, '/') w++ } // Copy element for r < n && p[r] != '/' { bufApp(&buf, p, w, p[r]) w++ r++ } } } // Re-append trailing slash if trailing && w > 1 { bufApp(&buf, p, w, '/') w++ } // If the original string was not modified (or only shortened at the end), // return the respective substring of the original string. // Otherwise return a new string from the buffer. if len(buf) == 0 { return p[:w] } return string(buf[:w]) } // Internal helper to lazily create a buffer if necessary. // Calls to this function get inlined. func bufApp(buf *[]byte, s string, w int, c byte) { b := *buf if len(b) == 0 { // No modification of the original string so far. // If the next character is the same as in the original string, we do // not yet have to allocate a buffer. if s[w] == c { return } // Otherwise use either the stack buffer, if it is large enough, or // allocate a new buffer on the heap, and copy all previous characters. if l := len(s); l > cap(b) { *buf = make([]byte, len(s)) } else { *buf = (*buf)[:l] } b = *buf copy(b, s[:w]) } b[w] = c } gin-1.6.3/path_test.go000066400000000000000000000056331365354716400146450ustar00rootroot00000000000000// Copyright 2013 Julien Schmidt. All rights reserved. // Based on the path package, Copyright 2009 The Go Authors. // Use of this source code is governed by a BSD-style license that can be found // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin import ( "strings" "testing" "github.com/stretchr/testify/assert" ) type cleanPathTest struct { path, result string } var cleanTests = []cleanPathTest{ // Already clean {"/", "/"}, {"/abc", "/abc"}, {"/a/b/c", "/a/b/c"}, {"/abc/", "/abc/"}, {"/a/b/c/", "/a/b/c/"}, // missing root {"", "/"}, {"a/", "/a/"}, {"abc", "/abc"}, {"abc/def", "/abc/def"}, {"a/b/c", "/a/b/c"}, // Remove doubled slash {"//", "/"}, {"/abc//", "/abc/"}, {"/abc/def//", "/abc/def/"}, {"/a/b/c//", "/a/b/c/"}, {"/abc//def//ghi", "/abc/def/ghi"}, {"//abc", "/abc"}, {"///abc", "/abc"}, {"//abc//", "/abc/"}, // Remove . elements {".", "/"}, {"./", "/"}, {"/abc/./def", "/abc/def"}, {"/./abc/def", "/abc/def"}, {"/abc/.", "/abc/"}, // Remove .. elements {"..", "/"}, {"../", "/"}, {"../../", "/"}, {"../..", "/"}, {"../../abc", "/abc"}, {"/abc/def/ghi/../jkl", "/abc/def/jkl"}, {"/abc/def/../ghi/../jkl", "/abc/jkl"}, {"/abc/def/..", "/abc"}, {"/abc/def/../..", "/"}, {"/abc/def/../../..", "/"}, {"/abc/def/../../..", "/"}, {"/abc/def/../../../ghi/jkl/../../../mno", "/mno"}, // Combinations {"abc/./../def", "/def"}, {"abc//./../def", "/def"}, {"abc/../../././../def", "/def"}, } func TestPathClean(t *testing.T) { for _, test := range cleanTests { assert.Equal(t, test.result, cleanPath(test.path)) assert.Equal(t, test.result, cleanPath(test.result)) } } func TestPathCleanMallocs(t *testing.T) { if testing.Short() { t.Skip("skipping malloc count in short mode") } for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) assert.EqualValues(t, allocs, 0) } } func BenchmarkPathClean(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { for _, test := range cleanTests { cleanPath(test.path) } } } func genLongPaths() (testPaths []cleanPathTest) { for i := 1; i <= 1234; i++ { ss := strings.Repeat("a", i) correctPath := "/" + ss testPaths = append(testPaths, cleanPathTest{ path: correctPath, result: correctPath, }, cleanPathTest{ path: ss, result: correctPath, }, cleanPathTest{ path: "//" + ss, result: correctPath, }, cleanPathTest{ path: "/" + ss + "/b/..", result: correctPath, }) } return } func TestPathCleanLong(t *testing.T) { cleanTests := genLongPaths() for _, test := range cleanTests { assert.Equal(t, test.result, cleanPath(test.path)) assert.Equal(t, test.result, cleanPath(test.result)) } } func BenchmarkPathCleanLong(b *testing.B) { cleanTests := genLongPaths() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { for _, test := range cleanTests { cleanPath(test.path) } } } gin-1.6.3/recovery.go000066400000000000000000000103671365354716400145100ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "fmt" "io" "io/ioutil" "log" "net" "net/http" "net/http/httputil" "os" "runtime" "strings" "time" ) var ( dunno = []byte("???") centerDot = []byte("·") dot = []byte(".") slash = []byte("/") ) // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) } // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one. func RecoveryWithWriter(out io.Writer) HandlerFunc { var logger *log.Logger if out != nil { logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags) } return func(c *Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } if logger != nil { stack := stack(3) httpRequest, _ := httputil.DumpRequest(c.Request, false) headers := strings.Split(string(httpRequest), "\r\n") for idx, header := range headers { current := strings.Split(header, ":") if current[0] == "Authorization" { headers[idx] = current[0] + ": *" } } if brokenPipe { logger.Printf("%s\n%s%s", err, string(httpRequest), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) } } // If the connection is dead, we can't write a status to it. if brokenPipe { c.Error(err.(error)) // nolint: errcheck c.Abort() } else { c.AbortWithStatus(http.StatusInternalServerError) } } }() c.Next() } } // stack returns a nicely formatted stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently // loaded file. var lines [][]byte var lastFile string for i := skip; ; i++ { // Skip the expected number of frames pc, file, line, ok := runtime.Caller(i) if !ok { break } // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { data, err := ioutil.ReadFile(file) if err != nil { continue } lines = bytes.Split(data, []byte{'\n'}) lastFile = file } fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) } return buf.Bytes() } // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed if n < 0 || n >= len(lines) { return dunno } return bytes.TrimSpace(lines[n]) } // function returns, if possible, the name of the function containing the PC. func function(pc uintptr) []byte { fn := runtime.FuncForPC(pc) if fn == nil { return dunno } name := []byte(fn.Name()) // The name includes the path name to the package, which is unnecessary // since the file name is already included. Plus, it has center dots. // That is, we see // runtime/debug.*T·ptrmethod // and want // *T.ptrmethod // Also the package path might contains dot (e.g. code.google.com/...), // so first eliminate the path prefix if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { name = name[lastSlash+1:] } if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } name = bytes.Replace(name, centerDot, dot, -1) return name } func timeFormat(t time.Time) string { var timeString = t.Format("2006/01/02 - 15:04:05") return timeString } gin-1.6.3/recovery_test.go000066400000000000000000000071321365354716400155430ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "fmt" "net" "net/http" "os" "strings" "syscall" "testing" "github.com/stretchr/testify/assert" ) func TestPanicClean(t *testing.T) { buffer := new(bytes.Buffer) router := New() password := "my-super-secret-password" router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(c *Context) { c.AbortWithStatus(http.StatusBadRequest) panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery", header{ Key: "Host", Value: "www.google.com", }, header{ Key: "Authorization", Value: fmt.Sprintf("Bearer %s", password), }, header{ Key: "Content-Type", Value: "application/json", }, ) // TEST assert.Equal(t, http.StatusBadRequest, w.Code) // Check the buffer does not have the secret key assert.NotContains(t, buffer.String(), password) } // TestPanicInHandler assert that panic has been recovered. func TestPanicInHandler(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(_ *Context) { panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") assert.NotContains(t, buffer.String(), "GET /recovery") // Debug mode prints the request SetMode(DebugMode) // RUN w = performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") SetMode(TestMode) } // TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. func TestPanicWithAbort(t *testing.T) { router := New() router.Use(RecoveryWithWriter(nil)) router.GET("/recovery", func(c *Context) { c.AbortWithStatus(http.StatusBadRequest) panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) } func TestSource(t *testing.T) { bs := source(nil, 0) assert.Equal(t, []byte("???"), bs) in := [][]byte{ []byte("Hello world."), []byte("Hi, gin.."), } bs = source(in, 10) assert.Equal(t, []byte("???"), bs) bs = source(in, 1) assert.Equal(t, []byte("Hello world."), bs) } func TestFunction(t *testing.T) { bs := function(1) assert.Equal(t, []byte("???"), bs) } // TestPanicWithBrokenPipe asserts that recovery specifically handles // writing responses to broken pipes func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 expectMsgs := map[syscall.Errno]string{ syscall.EPIPE: "broken pipe", syscall.ECONNRESET: "connection reset by peer", } for errno, expectMsg := range expectMsgs { t.Run(expectMsg, func(t *testing.T) { var buf bytes.Buffer router := New() router.Use(RecoveryWithWriter(&buf)) router.GET("/recovery", func(c *Context) { // Start writing response c.Header("X-Test", "Value") c.Status(expectCode) // Oops. Client connection closed e := &net.OpError{Err: &os.SyscallError{Err: errno}} panic(e) }) // RUN w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, expectCode, w.Code) assert.Contains(t, strings.ToLower(buf.String()), expectMsg) }) } } gin-1.6.3/render/000077500000000000000000000000001365354716400135735ustar00rootroot00000000000000gin-1.6.3/render/data.go000066400000000000000000000012001365354716400150240ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import "net/http" // Data contains ContentType and bytes data. type Data struct { ContentType string Data []byte } // Render (Data) writes data with custom ContentType. func (r Data) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) _, err = w.Write(r.Data) return } // WriteContentType (Data) writes custom ContentType. func (r Data) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } gin-1.6.3/render/html.go000066400000000000000000000050001365354716400150610ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "html/template" "net/http" ) // Delims represents a set of Left and Right delimiters for HTML template rendering. type Delims struct { // Left delimiter, defaults to {{. Left string // Right delimiter, defaults to }}. Right string } // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. type HTMLRender interface { // Instance returns an HTML instance. Instance(string, interface{}) Render } // HTMLProduction contains template reference and its delims. type HTMLProduction struct { Template *template.Template Delims Delims } // HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { Files []string Glob string Delims Delims FuncMap template.FuncMap } // HTML contains template reference and its name with given interface object. type HTML struct { Template *template.Template Name string Data interface{} } var htmlContentType = []string{"text/html; charset=utf-8"} // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, Name: name, Data: data, } } // Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), Name: name, Data: data, } } func (r HTMLDebug) loadTemplate() *template.Template { if r.FuncMap == nil { r.FuncMap = template.FuncMap{} } if len(r.Files) > 0 { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) } if r.Glob != "" { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") } // Render (HTML) executes template and writes its result with custom ContentType for response. func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) if r.Name == "" { return r.Template.Execute(w, r.Data) } return r.Template.ExecuteTemplate(w, r.Name, r.Data) } // WriteContentType (HTML) writes HTML ContentType. func (r HTML) WriteContentType(w http.ResponseWriter) { writeContentType(w, htmlContentType) } gin-1.6.3/render/json.go000066400000000000000000000116411365354716400150760ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "bytes" "fmt" "html/template" "net/http" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" ) // JSON contains the given interface object. type JSON struct { Data interface{} } // IndentedJSON contains the given interface object. type IndentedJSON struct { Data interface{} } // SecureJSON contains the given interface object and its prefix. type SecureJSON struct { Prefix string Data interface{} } // JsonpJSON contains the given interface object its callback. type JsonpJSON struct { Callback string Data interface{} } // AsciiJSON contains the given interface object. type AsciiJSON struct { Data interface{} } // SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string // PureJSON contains the given interface object. type PureJSON struct { Data interface{} } var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} // Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) } return } // WriteContentType (JSON) writes JSON ContentType. func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { return err } _, err = w.Write(jsonBytes) return err } // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.MarshalIndent(r.Data, "", " ") if err != nil { return err } _, err = w.Write(jsonBytes) return err } // WriteContentType (IndentedJSON) writes JSON ContentType. func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } // Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.Marshal(r.Data) if err != nil { return err } // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes, bytesconv.StringToBytes("]")) { _, err = w.Write(bytesconv.StringToBytes(r.Prefix)) if err != nil { return err } } _, err = w.Write(jsonBytes) return err } // WriteContentType (SecureJSON) writes JSON ContentType. func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } // Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) if err != nil { return err } if r.Callback == "" { _, err = w.Write(ret) return err } callback := template.JSEscapeString(r.Callback) _, err = w.Write(bytesconv.StringToBytes(callback)) if err != nil { return err } _, err = w.Write(bytesconv.StringToBytes("(")) if err != nil { return err } _, err = w.Write(ret) if err != nil { return err } _, err = w.Write(bytesconv.StringToBytes(");")) if err != nil { return err } return nil } // WriteContentType (JsonpJSON) writes Javascript ContentType. func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) if err != nil { return err } var buffer bytes.Buffer for _, r := range bytesconv.BytesToString(ret) { cvt := string(r) if r >= 128 { cvt = fmt.Sprintf("\\u%04x", int64(r)) } buffer.WriteString(cvt) } _, err = w.Write(buffer.Bytes()) return err } // WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } // Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) encoder := json.NewEncoder(w) encoder.SetEscapeHTML(false) return encoder.Encode(r.Data) } // WriteContentType (PureJSON) writes custom ContentType. func (r PureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } gin-1.6.3/render/msgpack.go000066400000000000000000000020541365354716400155500ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package render import ( "net/http" "github.com/ugorji/go/codec" ) var ( _ Render = MsgPack{} ) // MsgPack contains the given interface object. type MsgPack struct { Data interface{} } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} // WriteContentType (MsgPack) writes MsgPack ContentType. func (r MsgPack) WriteContentType(w http.ResponseWriter) { writeContentType(w, msgpackContentType) } // Render (MsgPack) encodes the given interface object and writes data with custom ContentType. func (r MsgPack) Render(w http.ResponseWriter) error { return WriteMsgPack(w, r.Data) } // WriteMsgPack writes MsgPack ContentType and encodes the given interface object. func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) var mh codec.MsgpackHandle return codec.NewEncoder(w, &mh).Encode(obj) } gin-1.6.3/render/protobuf.go000066400000000000000000000015351365354716400157660ustar00rootroot00000000000000// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "net/http" "github.com/golang/protobuf/proto" ) // ProtoBuf contains the given interface object. type ProtoBuf struct { Data interface{} } var protobufContentType = []string{"application/x-protobuf"} // Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType. func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) bytes, err := proto.Marshal(r.Data.(proto.Message)) if err != nil { return err } _, err = w.Write(bytes) return err } // WriteContentType (ProtoBuf) writes ProtoBuf ContentType. func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { writeContentType(w, protobufContentType) } gin-1.6.3/render/reader.go000066400000000000000000000022711365354716400153660ustar00rootroot00000000000000// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "io" "net/http" "strconv" ) // Reader contains the IO reader and its length, and custom ContentType and other headers. type Reader struct { ContentType string ContentLength int64 Reader io.Reader Headers map[string]string } // Render (Reader) writes data with custom ContentType and headers. func (r Reader) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) if r.ContentLength >= 0 { if r.Headers == nil { r.Headers = map[string]string{} } r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) } r.writeHeaders(w, r.Headers) _, err = io.Copy(w, r.Reader) return } // WriteContentType (Reader) writes custom ContentType. func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } // writeHeaders writes custom Header. func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { if header.Get(k) == "" { header.Set(k, v) } } } gin-1.6.3/render/reader_test.go000066400000000000000000000007711365354716400164300ustar00rootroot00000000000000// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/require" ) func TestReaderRenderNoHeaders(t *testing.T) { content := "test" r := Reader{ ContentLength: int64(len(content)), Reader: strings.NewReader(content), } err := r.Render(httptest.NewRecorder()) require.NoError(t, err) } gin-1.6.3/render/redirect.go000066400000000000000000000016111365354716400157220ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "fmt" "net/http" ) // Redirect contains the http request reference and redirects status code and location. type Redirect struct { Code int Request *http.Request Location string } // Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } http.Redirect(w, r.Request, r.Location, r.Code) return nil } // WriteContentType (Redirect) don't write any ContentType. func (r Redirect) WriteContentType(http.ResponseWriter) {} gin-1.6.3/render/render.go000066400000000000000000000020411365354716400153760ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import "net/http" // Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { // Render writes data with custom ContentType. Render(http.ResponseWriter) error // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) } var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} _ Render = Data{} _ Render = HTML{} _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = Reader{} _ Render = AsciiJSON{} _ Render = ProtoBuf{} ) func writeContentType(w http.ResponseWriter, value []string) { header := w.Header() if val := header["Content-Type"]; len(val) == 0 { header["Content-Type"] = value } } gin-1.6.3/render/render_msgpack_test.go000066400000000000000000000017401365354716400201470ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // +build !nomsgpack package render import ( "bytes" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) // TODO unit tests // test errors func TestRenderMsgPack(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } (MsgPack{data}).WriteContentType(w) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) err := (MsgPack{data}).Render(w) assert.NoError(t, err) h := new(codec.MsgpackHandle) assert.NotNil(t, h) buf := bytes.NewBuffer([]byte{}) assert.NotNil(t, buf) err = codec.NewEncoder(buf, h).Encode(data) assert.NoError(t, err) assert.Equal(t, w.Body.String(), string(buf.Bytes())) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } gin-1.6.3/render/render_test.go000066400000000000000000000322721365354716400164460ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "encoding/xml" "errors" "html/template" "net/http" "net/http/httptest" "strconv" "strings" "testing" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) // TODO unit tests // test errors func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", "html": "", } (JSON{data}).WriteContentType(w) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) err := (JSON{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderJSONPanics(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) } func TestRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", "bar": "foo", } err := (IndentedJSON{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderIndentedJSONPanics(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int err := (IndentedJSON{data}).Render(w) assert.Error(t, err) } func TestRenderSecureJSON(t *testing.T) { w1 := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } (SecureJSON{"while(1);", data}).WriteContentType(w1) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) err1 := (SecureJSON{"while(1);", data}).Render(w1) assert.NoError(t, err1) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() datas := []map[string]interface{}{{ "foo": "bar", }, { "bar": "foo", }} err2 := (SecureJSON{"while(1);", datas}).Render(w2) assert.NoError(t, err2) assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) } func TestRenderSecureJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int err := (SecureJSON{"while(1);", data}).Render(w) assert.Error(t, err) } func TestRenderJsonpJSON(t *testing.T) { w1 := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } (JsonpJSON{"x", data}).WriteContentType(w1) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) err1 := (JsonpJSON{"x", data}).Render(w1) assert.NoError(t, err1) assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() datas := []map[string]interface{}{{ "foo": "bar", }, { "bar": "foo", }} err2 := (JsonpJSON{"x", datas}).Render(w2) assert.NoError(t, err2) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } func TestRenderJsonpJSONError2(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } (JsonpJSON{"", data}).WriteContentType(w) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) e := (JsonpJSON{"", data}).Render(w) assert.NoError(t, e) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderJsonpJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int err := (JsonpJSON{"x", data}).Render(w) assert.Error(t, err) } func TestRenderAsciiJSON(t *testing.T) { w1 := httptest.NewRecorder() data1 := map[string]interface{}{ "lang": "GO语言", "tag": "
", } err := (AsciiJSON{data1}).Render(w1) assert.NoError(t, err) assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() data2 := float64(3.1415926) err = (AsciiJSON{data2}).Render(w2) assert.NoError(t, err) assert.Equal(t, "3.1415926", w2.Body.String()) } func TestRenderAsciiJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int assert.Error(t, (AsciiJSON{data}).Render(w)) } func TestRenderPureJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", "html": "", } err := (PureJSON{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{ Space: "", Local: "map", } if err := e.EncodeToken(start); err != nil { return err } for key, value := range h { elem := xml.StartElement{ Name: xml.Name{Space: "", Local: key}, Attr: []xml.Attr{}, } if err := e.EncodeElement(value, elem); err != nil { return err } } return e.EncodeToken(xml.EndElement{Name: start.Name}) } func TestRenderYAML(t *testing.T) { w := httptest.NewRecorder() data := ` a : Easy! b: c: 2 d: [3, 4] ` (YAML{data}).WriteContentType(w) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} // Hook MarshalYAML func (ft *fail) MarshalYAML() (interface{}, error) { return nil, errors.New("fail") } func TestRenderYAMLFail(t *testing.T) { w := httptest.NewRecorder() err := (YAML{&fail{}}).Render(w) assert.Error(t, err) } // test Protobuf rendering func TestRenderProtoBuf(t *testing.T) { w := httptest.NewRecorder() reps := []int64{int64(1), int64(2)} label := "test" data := &testdata.Test{ Label: &label, Reps: reps, } (ProtoBuf{data}).WriteContentType(w) protoData, err := proto.Marshal(data) assert.NoError(t, err) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) err = (ProtoBuf{data}).Render(w) assert.NoError(t, err) assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } func TestRenderProtoBufFail(t *testing.T) { w := httptest.NewRecorder() data := &testdata.Test{} err := (ProtoBuf{data}).Render(w) assert.Error(t, err) } func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ "foo": "bar", } (XML{data}).WriteContentType(w) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) err := (XML{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { req, err := http.NewRequest("GET", "/test-redirect", nil) assert.NoError(t, err) data1 := Redirect{ Code: http.StatusMovedPermanently, Request: req, Location: "/new/location", } w := httptest.NewRecorder() err = data1.Render(w) assert.NoError(t, err) data2 := Redirect{ Code: http.StatusOK, Request: req, Location: "/new/location", } w = httptest.NewRecorder() assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { err := data2.Render(w) assert.NoError(t, err) }) data3 := Redirect{ Code: http.StatusCreated, Request: req, Location: "/new/location", } w = httptest.NewRecorder() err = data3.Render(w) assert.NoError(t, err) // only improve coverage data2.WriteContentType(w) } func TestRenderData(t *testing.T) { w := httptest.NewRecorder() data := []byte("#!PNG some raw data") err := (Data{ ContentType: "image/png", Data: data, }).Render(w) assert.NoError(t, err) assert.Equal(t, "#!PNG some raw data", w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } func TestRenderString(t *testing.T) { w := httptest.NewRecorder() (String{ Format: "hello %s %d", Data: []interface{}{}, }).WriteContentType(w) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) err := (String{ Format: "hola %s %d", Data: []interface{}{"manu", 2}, }).Render(w) assert.NoError(t, err) assert.Equal(t, "hola manu 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderStringLenZero(t *testing.T) { w := httptest.NewRecorder() err := (String{ Format: "hola %s %d", Data: []interface{}{}, }).Render(w) assert.NoError(t, err) assert.Equal(t, "hola %s %d", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplate(t *testing.T) { w := httptest.NewRecorder() templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) htmlRender := HTMLProduction{Template: templ} instance := htmlRender.Instance("t", map[string]interface{}{ "name": "alexandernyquist", }) err := instance.Render(w) assert.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplateEmptyName(t *testing.T) { w := httptest.NewRecorder() templ := template.Must(template.New("").Parse(`Hello {{.name}}`)) htmlRender := HTMLProduction{Template: templ} instance := htmlRender.Instance("", map[string]interface{}{ "name": "alexandernyquist", }) err := instance.Render(w) assert.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, Glob: "", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ "name": "thinkerou", }) err := instance.Render(w) assert.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{Files: nil, Glob: "../testdata/template/hello*", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ "name": "thinkerou", }) err := instance.Render(w) assert.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugPanics(t *testing.T) { htmlRender := HTMLDebug{Files: nil, Glob: "", Delims: Delims{"{{", "}}"}, FuncMap: nil, } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } func TestRenderReader(t *testing.T) { w := httptest.NewRecorder() body := "#!PNG some raw data" headers := make(map[string]string) headers["Content-Disposition"] = `attachment; filename="filename.png"` headers["x-request-id"] = "requestId" err := (Reader{ ContentLength: int64(len(body)), ContentType: "image/png", Reader: strings.NewReader(body), Headers: headers, }).Render(w) assert.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } func TestRenderReaderNoContentLength(t *testing.T) { w := httptest.NewRecorder() body := "#!PNG some raw data" headers := make(map[string]string) headers["Content-Disposition"] = `attachment; filename="filename.png"` headers["x-request-id"] = "requestId" err := (Reader{ ContentLength: -1, ContentType: "image/png", Reader: strings.NewReader(body), Headers: headers, }).Render(w) assert.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.NotContains(t, "Content-Length", w.Header()) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } gin-1.6.3/render/text.go000066400000000000000000000020331365354716400151040ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "fmt" "io" "net/http" ) // String contains the given interface object slice and its format. type String struct { Format string Data []interface{} } var plainContentType = []string{"text/plain; charset=utf-8"} // Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { return WriteString(w, r.Format, r.Data) } // WriteContentType (String) writes Plain ContentType. func (r String) WriteContentType(w http.ResponseWriter) { writeContentType(w, plainContentType) } // WriteString writes data according to its format and write custom ContentType. func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { writeContentType(w, plainContentType) if len(data) > 0 { _, err = fmt.Fprintf(w, format, data...) return } _, err = io.WriteString(w, format) return } gin-1.6.3/render/xml.go000066400000000000000000000013431365354716400147230ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "encoding/xml" "net/http" ) // XML contains the given interface object. type XML struct { Data interface{} } var xmlContentType = []string{"application/xml; charset=utf-8"} // Render (XML) encodes the given interface object and writes data with custom ContentType. func (r XML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } // WriteContentType (XML) writes XML ContentType for response. func (r XML) WriteContentType(w http.ResponseWriter) { writeContentType(w, xmlContentType) } gin-1.6.3/render/yaml.go000066400000000000000000000014701365354716400150660ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package render import ( "net/http" "gopkg.in/yaml.v2" ) // YAML contains the given interface object. type YAML struct { Data interface{} } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} // Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { r.WriteContentType(w) bytes, err := yaml.Marshal(r.Data) if err != nil { return err } _, err = w.Write(bytes) return err } // WriteContentType (YAML) writes YAML ContentType for response. func (r YAML) WriteContentType(w http.ResponseWriter) { writeContentType(w, yamlContentType) } gin-1.6.3/response_writer.go000066400000000000000000000052121365354716400160750ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bufio" "io" "net" "net/http" ) const ( noWritten = -1 defaultStatus = http.StatusOK ) // ResponseWriter ... type ResponseWriter interface { http.ResponseWriter http.Hijacker http.Flusher http.CloseNotifier // Returns the HTTP response status code of the current request. Status() int // Returns the number of bytes already written into the response http body. // See Written() Size() int // Writes the string into the response body. WriteString(string) (int, error) // Returns true if the response body was already written. Written() bool // Forces to write the http header (status code + headers). WriteHeaderNow() // get the http.Pusher for server push Pusher() http.Pusher } type responseWriter struct { http.ResponseWriter size int status int } var _ ResponseWriter = &responseWriter{} func (w *responseWriter) reset(writer http.ResponseWriter) { w.ResponseWriter = writer w.size = noWritten w.status = defaultStatus } func (w *responseWriter) WriteHeader(code int) { if code > 0 && w.status != code { if w.Written() { debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code) } w.status = code } } func (w *responseWriter) WriteHeaderNow() { if !w.Written() { w.size = 0 w.ResponseWriter.WriteHeader(w.status) } } func (w *responseWriter) Write(data []byte) (n int, err error) { w.WriteHeaderNow() n, err = w.ResponseWriter.Write(data) w.size += n return } func (w *responseWriter) WriteString(s string) (n int, err error) { w.WriteHeaderNow() n, err = io.WriteString(w.ResponseWriter, s) w.size += n return } func (w *responseWriter) Status() int { return w.status } func (w *responseWriter) Size() int { return w.size } func (w *responseWriter) Written() bool { return w.size != noWritten } // Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size < 0 { w.size = 0 } return w.ResponseWriter.(http.Hijacker).Hijack() } // CloseNotify implements the http.CloseNotify interface. func (w *responseWriter) CloseNotify() <-chan bool { return w.ResponseWriter.(http.CloseNotifier).CloseNotify() } // Flush implements the http.Flush interface. func (w *responseWriter) Flush() { w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } func (w *responseWriter) Pusher() (pusher http.Pusher) { if pusher, ok := w.ResponseWriter.(http.Pusher); ok { return pusher } return nil } gin-1.6.3/response_writer_test.go000066400000000000000000000066541365354716400171470ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) // TODO // func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { // func (w *responseWriter) CloseNotify() <-chan bool { // func (w *responseWriter) Flush() { var _ ResponseWriter = &responseWriter{} var _ http.ResponseWriter = &responseWriter{} var _ http.ResponseWriter = ResponseWriter(&responseWriter{}) var _ http.Hijacker = ResponseWriter(&responseWriter{}) var _ http.Flusher = ResponseWriter(&responseWriter{}) var _ http.CloseNotifier = ResponseWriter(&responseWriter{}) func init() { SetMode(TestMode) } func TestResponseWriterReset(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} var w ResponseWriter = writer writer.reset(testWriter) assert.Equal(t, -1, writer.size) assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, testWriter, writer.ResponseWriter) assert.Equal(t, -1, w.Size()) assert.Equal(t, http.StatusOK, w.Status()) assert.False(t, w.Written()) } func TestResponseWriterWriteHeader(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWriter) w := ResponseWriter(writer) w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) assert.Equal(t, http.StatusMultipleChoices, w.Status()) assert.NotEqual(t, http.StatusMultipleChoices, testWriter.Code) w.WriteHeader(-1) assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWriter) w := ResponseWriter(writer) w.WriteHeader(http.StatusMultipleChoices) w.WriteHeaderNow() assert.True(t, w.Written()) assert.Equal(t, 0, w.Size()) assert.Equal(t, http.StatusMultipleChoices, testWriter.Code) writer.size = 10 w.WriteHeaderNow() assert.Equal(t, 10, w.Size()) } func TestResponseWriterWrite(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWriter) w := ResponseWriter(writer) n, err := w.Write([]byte("hola")) assert.Equal(t, 4, n) assert.Equal(t, 4, w.Size()) assert.Equal(t, http.StatusOK, w.Status()) assert.Equal(t, http.StatusOK, testWriter.Code) assert.Equal(t, "hola", testWriter.Body.String()) assert.NoError(t, err) n, err = w.Write([]byte(" adios")) assert.Equal(t, 6, n) assert.Equal(t, 10, w.Size()) assert.Equal(t, "hola adios", testWriter.Body.String()) assert.NoError(t, err) } func TestResponseWriterHijack(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWriter) w := ResponseWriter(writer) assert.Panics(t, func() { _, _, err := w.Hijack() assert.NoError(t, err) }) assert.True(t, w.Written()) assert.Panics(t, func() { w.CloseNotify() }) w.Flush() } func TestResponseWriterFlush(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writer := &responseWriter{} writer.reset(w) writer.WriteHeader(http.StatusInternalServerError) writer.Flush() })) defer testServer.Close() // should return 500 resp, err := http.Get(testServer.URL) assert.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) } gin-1.6.3/routergroup.go000066400000000000000000000203251365354716400152420ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "net/http" "path" "regexp" "strings" ) // IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } // IRoutes defines all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes Handle(string, string, ...HandlerFunc) IRoutes Any(string, ...HandlerFunc) IRoutes GET(string, ...HandlerFunc) IRoutes POST(string, ...HandlerFunc) IRoutes DELETE(string, ...HandlerFunc) IRoutes PATCH(string, ...HandlerFunc) IRoutes PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes Static(string, string) IRoutes StaticFS(string, http.FileSystem) IRoutes } // RouterGroup is used internally to configure router, a RouterGroup is associated with // a prefix and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string engine *Engine root bool } var _ IRouter = &RouterGroup{} // Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() } // Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. // For example, all the routes that use a common middleware for authorization could be grouped. func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return &RouterGroup{ Handlers: group.combineHandlers(handlers), basePath: group.calculateAbsolutePath(relativePath), engine: group.engine, } } // BasePath returns the base path of router group. // For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". func (group *RouterGroup) BasePath() string { return group.basePath } func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() } // Handle registers a new request handle and middleware with the given path and method. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. // See the example code in GitHub. // // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // functions can be used. // // This function is intended for bulk loading and to allow the usage of less // frequently used, non-standardized or custom methods (e.g. for internal // communication with a proxy). func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil { panic("http method " + httpMethod + " is not valid") } return group.handle(httpMethod, relativePath, handlers) } // POST is a shortcut for router.Handle("POST", path, handle). func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPost, relativePath, handlers) } // GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodGet, relativePath, handlers) } // DELETE is a shortcut for router.Handle("DELETE", path, handle). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodDelete, relativePath, handlers) } // PATCH is a shortcut for router.Handle("PATCH", path, handle). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPatch, relativePath, handlers) } // PUT is a shortcut for router.Handle("PUT", path, handle). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPut, relativePath, handlers) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodOptions, relativePath, handlers) } // HEAD is a shortcut for router.Handle("HEAD", path, handle). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodHead, relativePath, handlers) } // Any registers a route that matches all the HTTP methods. // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { group.handle(http.MethodGet, relativePath, handlers) group.handle(http.MethodPost, relativePath, handlers) group.handle(http.MethodPut, relativePath, handlers) group.handle(http.MethodPatch, relativePath, handlers) group.handle(http.MethodHead, relativePath, handlers) group.handle(http.MethodOptions, relativePath, handlers) group.handle(http.MethodDelete, relativePath, handlers) group.handle(http.MethodConnect, relativePath, handlers) group.handle(http.MethodTrace, relativePath, handlers) return group.returnObj() } // StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { panic("URL parameters can not be used when serving a static file") } handler := func(c *Context) { c.File(filepath) } group.GET(relativePath, handler) group.HEAD(relativePath, handler) return group.returnObj() } // Static serves files from the given file system root. // Internally a http.FileServer is used, therefore http.NotFound is used instead // of the Router's NotFound handler. // To use the operating system's file system implementation, // use : // router.Static("/static", "/var/www") func (group *RouterGroup) Static(relativePath, root string) IRoutes { return group.StaticFS(relativePath, Dir(root, false)) } // StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead. // Gin by default user: gin.Dir() func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { panic("URL parameters can not be used when serving a static folder") } handler := group.createStaticHandler(relativePath, fs) urlPattern := path.Join(relativePath, "/*filepath") // Register GET and HEAD handlers group.GET(urlPattern, handler) group.HEAD(urlPattern, handler) return group.returnObj() } func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath := group.calculateAbsolutePath(relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { if _, nolisting := fs.(*onlyfilesFS); nolisting { c.Writer.WriteHeader(http.StatusNotFound) } file := c.Param("filepath") // Check if file exists and/or if we have permission to access it f, err := fs.Open(file) if err != nil { c.Writer.WriteHeader(http.StatusNotFound) c.handlers = group.engine.noRoute // Reset index c.index = -1 return } f.Close() fileServer.ServeHTTP(c.Writer, c.Request) } } func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers } func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { return joinPaths(group.basePath, relativePath) } func (group *RouterGroup) returnObj() IRoutes { if group.root { return group.engine } return group } gin-1.6.3/routergroup_test.go000066400000000000000000000110011365354716400162700ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "net/http" "testing" "github.com/stretchr/testify/assert" ) func init() { SetMode(TestMode) } func TestRouterGroupBasic(t *testing.T) { router := New() group := router.Group("/hola", func(c *Context) {}) group.Use(func(c *Context) {}) assert.Len(t, group.Handlers, 2) assert.Equal(t, "/hola", group.BasePath()) assert.Equal(t, router, group.engine) group2 := group.Group("manu") group2.Use(func(c *Context) {}, func(c *Context) {}) assert.Len(t, group2.Handlers, 4) assert.Equal(t, "/hola/manu", group2.BasePath()) assert.Equal(t, router, group2.engine) } func TestRouterGroupBasicHandle(t *testing.T) { performRequestInGroup(t, http.MethodGet) performRequestInGroup(t, http.MethodPost) performRequestInGroup(t, http.MethodPut) performRequestInGroup(t, http.MethodPatch) performRequestInGroup(t, http.MethodDelete) performRequestInGroup(t, http.MethodHead) performRequestInGroup(t, http.MethodOptions) } func performRequestInGroup(t *testing.T, method string) { router := New() v1 := router.Group("v1", func(c *Context) {}) assert.Equal(t, "/v1", v1.BasePath()) login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {}) assert.Equal(t, "/v1/login/", login.BasePath()) handler := func(c *Context) { c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index) } switch method { case http.MethodGet: v1.GET("/test", handler) login.GET("/test", handler) case http.MethodPost: v1.POST("/test", handler) login.POST("/test", handler) case http.MethodPut: v1.PUT("/test", handler) login.PUT("/test", handler) case http.MethodPatch: v1.PATCH("/test", handler) login.PATCH("/test", handler) case http.MethodDelete: v1.DELETE("/test", handler) login.DELETE("/test", handler) case http.MethodHead: v1.HEAD("/test", handler) login.HEAD("/test", handler) case http.MethodOptions: v1.OPTIONS("/test", handler) login.OPTIONS("/test", handler) default: panic("unknown method") } w := performRequest(router, method, "/v1/login/test") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) w = performRequest(router, method, "/v1/test") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } func TestRouterGroupInvalidStatic(t *testing.T) { router := New() assert.Panics(t, func() { router.Static("/path/:param", "/") }) assert.Panics(t, func() { router.Static("/path/*param", "/") }) } func TestRouterGroupInvalidStaticFile(t *testing.T) { router := New() assert.Panics(t, func() { router.StaticFile("/path/:param", "favicon.ico") }) assert.Panics(t, func() { router.StaticFile("/path/*param", "favicon.ico") }) } func TestRouterGroupTooManyHandlers(t *testing.T) { router := New() handlers1 := make([]HandlerFunc, 40) router.Use(handlers1...) handlers2 := make([]HandlerFunc, 26) assert.Panics(t, func() { router.Use(handlers2...) }) assert.Panics(t, func() { router.GET("/", handlers2...) }) } func TestRouterGroupBadMethod(t *testing.T) { router := New() assert.Panics(t, func() { router.Handle(http.MethodGet, "/") }) assert.Panics(t, func() { router.Handle(" GET", "/") }) assert.Panics(t, func() { router.Handle("GET ", "/") }) assert.Panics(t, func() { router.Handle("", "/") }) assert.Panics(t, func() { router.Handle("PO ST", "/") }) assert.Panics(t, func() { router.Handle("1GET", "/") }) assert.Panics(t, func() { router.Handle("PATCh", "/") }) } func TestRouterGroupPipeline(t *testing.T) { router := New() testRoutesInterface(t, router) v1 := router.Group("/v1") testRoutesInterface(t, v1) } func testRoutesInterface(t *testing.T, r IRoutes) { handler := func(c *Context) {} assert.Equal(t, r, r.Use(handler)) assert.Equal(t, r, r.Handle(http.MethodGet, "/handler", handler)) assert.Equal(t, r, r.Any("/any", handler)) assert.Equal(t, r, r.GET("/", handler)) assert.Equal(t, r, r.POST("/", handler)) assert.Equal(t, r, r.DELETE("/", handler)) assert.Equal(t, r, r.PATCH("/", handler)) assert.Equal(t, r, r.PUT("/", handler)) assert.Equal(t, r, r.OPTIONS("/", handler)) assert.Equal(t, r, r.HEAD("/", handler)) assert.Equal(t, r, r.StaticFile("/file", ".")) assert.Equal(t, r, r.Static("/static", ".")) assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false))) } gin-1.6.3/routes_test.go000066400000000000000000000435101365354716400152260ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) type header struct { Key string Value string } func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { req := httptest.NewRequest(method, path, nil) for _, h := range headers { req.Header.Add(h.Key, h.Value) } w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } func testRouteOK(method string, t *testing.T) { passed := false passedAny := false r := New() r.Any("/test2", func(c *Context) { passedAny = true }) r.Handle(method, "/test", func(c *Context) { passed = true }) w := performRequest(r, method, "/test") assert.True(t, passed) assert.Equal(t, http.StatusOK, w.Code) performRequest(r, method, "/test2") assert.True(t, passedAny) } // TestSingleRouteOK tests that POST route is correctly invoked. func testRouteNotOK(method string, t *testing.T) { passed := false router := New() router.Handle(method, "/test_2", func(c *Context) { passed = true }) w := performRequest(router, method, "/test") assert.False(t, passed) assert.Equal(t, http.StatusNotFound, w.Code) } // TestSingleRouteOK tests that POST route is correctly invoked. func testRouteNotOK2(method string, t *testing.T) { passed := false router := New() router.HandleMethodNotAllowed = true var methodRoute string if method == http.MethodPost { methodRoute = http.MethodGet } else { methodRoute = http.MethodPost } router.Handle(methodRoute, "/test", func(c *Context) { passed = true }) w := performRequest(router, method, "/test") assert.False(t, passed) assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } func TestRouterMethod(t *testing.T) { router := New() router.PUT("/hey2", func(c *Context) { c.String(http.StatusOK, "sup2") }) router.PUT("/hey", func(c *Context) { c.String(http.StatusOK, "called") }) router.PUT("/hey3", func(c *Context) { c.String(http.StatusOK, "sup3") }) w := performRequest(router, http.MethodPut, "/hey") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } func TestRouterGroupRouteOK(t *testing.T) { testRouteOK(http.MethodGet, t) testRouteOK(http.MethodPost, t) testRouteOK(http.MethodPut, t) testRouteOK(http.MethodPatch, t) testRouteOK(http.MethodHead, t) testRouteOK(http.MethodOptions, t) testRouteOK(http.MethodDelete, t) testRouteOK(http.MethodConnect, t) testRouteOK(http.MethodTrace, t) } func TestRouteNotOK(t *testing.T) { testRouteNotOK(http.MethodGet, t) testRouteNotOK(http.MethodPost, t) testRouteNotOK(http.MethodPut, t) testRouteNotOK(http.MethodPatch, t) testRouteNotOK(http.MethodHead, t) testRouteNotOK(http.MethodOptions, t) testRouteNotOK(http.MethodDelete, t) testRouteNotOK(http.MethodConnect, t) testRouteNotOK(http.MethodTrace, t) } func TestRouteNotOK2(t *testing.T) { testRouteNotOK2(http.MethodGet, t) testRouteNotOK2(http.MethodPost, t) testRouteNotOK2(http.MethodPut, t) testRouteNotOK2(http.MethodPatch, t) testRouteNotOK2(http.MethodHead, t) testRouteNotOK2(http.MethodOptions, t) testRouteNotOK2(http.MethodDelete, t) testRouteNotOK2(http.MethodConnect, t) testRouteNotOK2(http.MethodTrace, t) } func TestRouteRedirectTrailingSlash(t *testing.T) { router := New() router.RedirectFixedPath = false router.RedirectTrailingSlash = true router.GET("/path", func(c *Context) {}) router.GET("/path2/", func(c *Context) {}) router.POST("/path3", func(c *Context) {}) router.PUT("/path4/", func(c *Context) {}) w := performRequest(router, http.MethodGet, "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, http.MethodPost, "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, http.MethodPut, "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, http.MethodGet, "/path2/") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, http.MethodPost, "/path3") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, http.MethodPut, "/path4/") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, http.MethodGet, "/path/") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, http.MethodPost, "/path3/") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, http.MethodPut, "/path4") assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRedirectFixedPath(t *testing.T) { router := New() router.RedirectFixedPath = true router.RedirectTrailingSlash = false router.GET("/path", func(c *Context) {}) router.GET("/Path2", func(c *Context) {}) router.POST("/PATH3", func(c *Context) {}) router.POST("/Path4/", func(c *Context) {}) w := performRequest(router, http.MethodGet, "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, http.MethodPost, "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, http.MethodPost, "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } // TestContextParamsGet tests that a parameter can be parsed from the URL. func TestRouteParamsByName(t *testing.T) { name := "" lastName := "" wild := "" router := New() router.GET("/test/:name/:last_name/*wild", func(c *Context) { name = c.Params.ByName("name") lastName = c.Params.ByName("last_name") var ok bool wild, ok = c.Params.Get("wild") assert.True(t, ok) assert.Equal(t, name, c.Param("name")) assert.Equal(t, name, c.Param("name")) assert.Equal(t, lastName, c.Param("last_name")) assert.Empty(t, c.Param("wtf")) assert.Empty(t, c.Params.ByName("wtf")) wtf, ok := c.Params.Get("wtf") assert.Empty(t, wtf) assert.False(t, ok) }) w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) assert.Equal(t, "smith", lastName) assert.Equal(t, "/is/super/great", wild) } // TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes. func TestRouteParamsByNameWithExtraSlash(t *testing.T) { name := "" lastName := "" wild := "" router := New() router.RemoveExtraSlash = true router.GET("/test/:name/:last_name/*wild", func(c *Context) { name = c.Params.ByName("name") lastName = c.Params.ByName("last_name") var ok bool wild, ok = c.Params.Get("wild") assert.True(t, ok) assert.Equal(t, name, c.Param("name")) assert.Equal(t, name, c.Param("name")) assert.Equal(t, lastName, c.Param("last_name")) assert.Empty(t, c.Param("wtf")) assert.Empty(t, c.Params.ByName("wtf")) wtf, ok := c.Params.Get("wtf") assert.Empty(t, wtf) assert.False(t, ok) }) w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) assert.Equal(t, "smith", lastName) assert.Equal(t, "/is/super/great", wild) } // TestHandleStaticFile - ensure the static file handles properly func TestRouteStaticFile(t *testing.T) { // SETUP file testRoot, _ := os.Getwd() f, err := ioutil.TempFile(testRoot, "") if err != nil { t.Error(err) } defer os.Remove(f.Name()) _, err = f.WriteString("Gin Web Framework") assert.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) // SETUP gin router := New() router.Static("/using_static", dir) router.StaticFile("/result", f.Name()) w := performRequest(router, http.MethodGet, "/using_static/"+filename) w2 := performRequest(router, http.MethodGet, "/result") assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) w4 := performRequest(router, http.MethodHead, "/result") assert.Equal(t, w3, w4) assert.Equal(t, http.StatusOK, w3.Code) } // TestHandleStaticDir - ensure the root/sub dir handles properly func TestRouteStaticListingDir(t *testing.T) { router := New() router.StaticFS("/", Dir("./", true)) w := performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestHandleHeadToDir - ensure the root/sub dir handles properly func TestRouteStaticNoListing(t *testing.T) { router := New() router.Static("/", "./") w := performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") } func TestRouterMiddlewareAndStatic(t *testing.T) { router := New() static := router.Group("/", func(c *Context) { c.Writer.Header().Add("Last-Modified", "Mon, 02 Jan 2006 15:04:05 MST") c.Writer.Header().Add("Expires", "Mon, 02 Jan 2006 15:04:05 MST") c.Writer.Header().Add("X-GIN", "Gin Framework") }) static.Static("/", "./") w := performRequest(router, http.MethodGet, "/gin.go") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) } func TestRouteNotAllowedEnabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = true router.POST("/path", func(c *Context) {}) w := performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, "responseText", w.Body.String()) assert.Equal(t, http.StatusTeapot, w.Code) } func TestRouteNotAllowedEnabled2(t *testing.T) { router := New() router.HandleMethodNotAllowed = true // add one methodTree to trees router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) router.GET("/path2", func(c *Context) {}) w := performRequest(router, http.MethodPost, "/path2") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) w := performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { router := New() router.RemoveExtraSlash = true router.GET("/path", func(c *Context) {}) router.GET("/", func(c *Context) {}) testRoutes := []struct { route string code int location string }{ {"/../path", http.StatusOK, ""}, // CleanPath {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } } func TestRouterNotFound(t *testing.T) { router := New() router.RedirectFixedPath = true router.GET("/path", func(c *Context) {}) router.GET("/dir/", func(c *Context) {}) router.GET("/", func(c *Context) {}) testRoutes := []struct { route string code int location string }{ {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ {"/../path", http.StatusMovedPermanently, "/path"}, // Without CleanPath {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } // Test custom not found handler var notFound bool router.NoRoute(func(c *Context) { c.AbortWithStatus(http.StatusNotFound) notFound = true }) w := performRequest(router, http.MethodGet, "/nope") assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) w = performRequest(router, http.MethodPatch, "/path/") assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) w = performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouterStaticFSNotFound(t *testing.T) { router := New() router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.NoRoute(func(c *Context) { c.String(404, "non existent") }) w := performRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) w = performRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) } func TestRouterStaticFSFileNotFound(t *testing.T) { router := New() router.StaticFS("/", http.FileSystem(http.Dir("."))) assert.NotPanics(t, func() { performRequest(router, http.MethodGet, "/nonexistent") }) } // Reproduction test for the bug of issue #1805 func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { router := New() // Middleware must be called just only once by per request. middlewareCalledNum := 0 router.Use(func(c *Context) { middlewareCalledNum += 1 }) router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) // First access performRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, 1, middlewareCalledNum) // Second access performRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, 2, middlewareCalledNum) } func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true route.POST("/project/:name/build/:num", func(c *Context) { name := c.Params.ByName("name") num := c.Params.ByName("num") assert.Equal(t, name, c.Param("name")) assert.Equal(t, num, c.Param("num")) assert.Equal(t, "Some/Other/Project", name) assert.Equal(t, "222", num) }) w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222") assert.Equal(t, http.StatusOK, w.Code) } func TestRouteRawPathNoUnescape(t *testing.T) { route := New() route.UseRawPath = true route.UnescapePathValues = false route.POST("/project/:name/build/:num", func(c *Context) { name := c.Params.ByName("name") num := c.Params.ByName("num") assert.Equal(t, name, c.Param("name")) assert.Equal(t, num, c.Param("num")) assert.Equal(t, "Some%2FOther%2FProject", name) assert.Equal(t, "333", num) }) w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333") assert.Equal(t, http.StatusOK, w.Code) } func TestRouteServeErrorWithWriteHeader(t *testing.T) { route := New() route.Use(func(c *Context) { c.Status(421) c.Next() }) w := performRequest(route, http.MethodGet, "/NotFound") assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } func TestRouteContextHoldsFullPath(t *testing.T) { router := New() // Test routes routes := []string{ "/simple", "/project/:name", "/", "/news/home", "/news", "/simple-two/one", "/simple-two/one-two", "/project/:name/build/*params", "/project/:name/bui", } for _, route := range routes { actualRoute := route router.GET(route, func(c *Context) { // For each defined route context should contain its full path assert.Equal(t, actualRoute, c.FullPath()) c.AbortWithStatus(http.StatusOK) }) } for _, route := range routes { w := performRequest(router, http.MethodGet, route) assert.Equal(t, http.StatusOK, w.Code) } // Test not found router.Use(func(c *Context) { // For not found routes full path is empty assert.Equal(t, "", c.FullPath()) }) w := performRequest(router, http.MethodGet, "/not-found") assert.Equal(t, http.StatusNotFound, w.Code) } gin-1.6.3/test_helpers.go000066400000000000000000000006541365354716400153510ustar00rootroot00000000000000// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() c = r.allocateContext() c.reset() c.writermem.reset(w) return } gin-1.6.3/testdata/000077500000000000000000000000001365354716400141255ustar00rootroot00000000000000gin-1.6.3/testdata/certificate/000077500000000000000000000000001365354716400164075ustar00rootroot00000000000000gin-1.6.3/testdata/certificate/cert.pem000066400000000000000000000020761365354716400200540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC9DCCAdygAwIBAgIQUNSK+OxWHYYFxHVJV0IlpDANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MTExNjEyMDA0N1oXDTE4MTExNjEyMDA0 N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAKmyj/YZpD59Bpy4w3qf6VzMw9uUBsWp+IP4kl7z5cmGHYUHn/YopTLH vR23GAB12p6Km5QWzCBuJF4j61PJXHfg3/rjShZ77JcQ3kzxuy1iKDI+DNKN7Klz rdjJ49QD0lczZHeBvvCL7JsJFKFjGy62rnStuW8LaIEdtjXT+GUZTxJh6G7yPYfD MS1IsdMQGOdbGwNa+qogMuQPh0TzHw+C73myKrjY6pREijknMC/rnIMz9dLPt6Kl xXy4br443dpY6dYGIhDuKhROT+vZ05HKasuuQUFhY7v/KoUpEZMB9rfUSzjQ5fak eDUAMniXRcd+DmwvboG2TI6ixmuPK+ECAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgWg MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocE fwAAATANBgkqhkiG9w0BAQsFAAOCAQEAMXOLvj7BFsxdbcfRPBd0OFrH/8lI7vPV LRcJ6r5iv0cnNvZXXbIOQLbg4clJAWjoE08nRm1KvNXhKdns0ELEV86YN2S6jThq rIGrBqKtaJLB3M9BtDSiQ6SGPLYrWvmhj3Avi8PbSGy51bpGbqopd16j6LYU7Cp2 TefMRlOAFtHojpCVon1CMpqcNxS0WNlQ3lUBSrw3HB0o12x++roja2ibF54tSHXB KUuadoEzN+mMBwenEBychmAGzdiG4GQHRmhigh85+mtW6UMGiqyCZHs0EgE9FCLL sRrsTI/VOzLz6lluygXkOsXrP+PP0SvmE3eylWjj9e2nj/u/Cy2YKg== -----END CERTIFICATE----- gin-1.6.3/testdata/certificate/key.pem000066400000000000000000000032131365354716400177010ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAqbKP9hmkPn0GnLjDep/pXMzD25QGxan4g/iSXvPlyYYdhQef 9iilMse9HbcYAHXanoqblBbMIG4kXiPrU8lcd+Df+uNKFnvslxDeTPG7LWIoMj4M 0o3sqXOt2Mnj1APSVzNkd4G+8IvsmwkUoWMbLraudK25bwtogR22NdP4ZRlPEmHo bvI9h8MxLUix0xAY51sbA1r6qiAy5A+HRPMfD4LvebIquNjqlESKOScwL+ucgzP1 0s+3oqXFfLhuvjjd2ljp1gYiEO4qFE5P69nTkcpqy65BQWFju/8qhSkRkwH2t9RL ONDl9qR4NQAyeJdFx34ObC9ugbZMjqLGa48r4QIDAQABAoIBAD5mhd+GMEo2KU9J 9b/Ku8I/HapJtW/L/7Fvn0tBPncrVQGM+zpGWfDhV95sbGwG6lwwNeNvuqIWPlNL vAY0XkdKrrIQEDdSXH50WnpKzXxzwrou7QIj5Cmvevbjzl4xBZDBOilj0XWczmV4 IljyG5XC4UXQeAaoWEZaSZ1jk8yAt2Zq1Hgg7HqhHsK/arWXBgax+4K5nV/s9gZx yjKU9mXTIs7k/aNnZqwQKqcZF+l3mvbZttOaFwsP14H0I8OFWhnM9hie54Dejqxi f4/llNxDqUs6lqJfP3qNxtORLcFe75M+Yl8v7g2hkjtLdZBakPzSTEx3TAK/UHgi aM8DdxECgYEA3fmg/PI4EgUEj0C3SCmQXR/CnQLMUQgb54s0asp4akvp+M7YCcr1 pQd3HFUpBwhBcJg5LeSe87vLupY7pHCKk56cl9WY6hse0b9sP/7DWJuGiO62m0E0 vNjQ2jpG99oR2ROIHHeWsGCpGLmrRT/kY+vR3M+AOLZniXlOCw8k0aUCgYEAw7WL XFWLxgZYQYilywqrQmfv1MBfaUCvykO6oWB+f6mmnihSFjecI+nDw/b3yXVYGEgy 0ebkuw0jP8suC8wBqX9WuXj+9nZNomJRssJyOMiEhDEqUiTztFPSp9pdruoakLTh Wk1p9NralOqGPUmxpXlFKVmYRTUbluikVxDypI0CgYBn6sqEQH0hann0+o4TWWn9 PrYkPUAbm1k8771tVTZERR/W3Dbldr/DL5iCihe39BR2urziEEqdvkglJNntJMar TzDuIBADYQjvltb9qq4XGFBGYMLaMg+XbUVxNKEuvUdnwa4R7aZ9EfN34MwekkfA w5Cu9/GGG1ajVEfGA6PwBQKBgA3o71jGs8KFXOx7e90sivOTU5Z5fc6LTHNB0Rf7 NcJ5GmCPWRY/KZfb25AoE4B8GKDRMNt+X69zxZeZJ1KrU0rqxA02rlhyHB54gnoE G/4xMkn6/JkOC0w70PMhMBtohC7YzFOQwQEoNPT0nkno3Pl33xSLS6lPlwBo1JVj nPtZAoGACXNLXYkR5vexE+w6FGl59r4RQhu1XU8Mr5DIHeB7kXPN3RKbS201M+Tb SB5jbu0iDV477XkzSNmhaksFf2wM9MT6CaE+8n3UU5tMa+MmBGgwYTp/i9HkqVh5 jjpJifn1VWBINd4cpNzwCg9LXoo0tbtUPWwGzqVeyo/YE5GIHGo= -----END RSA PRIVATE KEY----- gin-1.6.3/testdata/protoexample/000077500000000000000000000000001365354716400166445ustar00rootroot00000000000000gin-1.6.3/testdata/protoexample/test.pb.go000066400000000000000000000047571365354716400205670ustar00rootroot00000000000000// Code generated by protoc-gen-go. // source: test.proto // DO NOT EDIT! /* Package protoexample is a generated protocol buffer package. It is generated from these files: test.proto It has these top-level messages: Test */ package protoexample import proto "github.com/golang/protobuf/proto" import math "math" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = math.Inf type FOO int32 const ( FOO_X FOO = 17 ) var FOO_name = map[int32]string{ 17: "X", } var FOO_value = map[string]int32{ "X": 17, } func (x FOO) Enum() *FOO { p := new(FOO) *p = x return p } func (x FOO) String() string { return proto.EnumName(FOO_name, int32(x)) } func (x *FOO) UnmarshalJSON(data []byte) error { value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") if err != nil { return err } *x = FOO(value) return nil } type Test struct { Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} const Default_Test_Type int32 = 77 func (m *Test) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } return "" } func (m *Test) GetType() int32 { if m != nil && m.Type != nil { return *m.Type } return Default_Test_Type } func (m *Test) GetReps() []int64 { if m != nil { return m.Reps } return nil } func (m *Test) GetOptionalgroup() *Test_OptionalGroup { if m != nil { return m.Optionalgroup } return nil } type Test_OptionalGroup struct { RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } func (*Test_OptionalGroup) ProtoMessage() {} func (m *Test_OptionalGroup) GetRequiredField() string { if m != nil && m.RequiredField != nil { return *m.RequiredField } return "" } func init() { proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value) } gin-1.6.3/testdata/protoexample/test.proto000066400000000000000000000003571365354716400207150ustar00rootroot00000000000000package protoexample; enum FOO {X=17;}; message Test { required string label = 1; optional int32 type = 2[default=77]; repeated int64 reps = 3; optional group OptionalGroup = 4{ required string RequiredField = 5; } } gin-1.6.3/testdata/template/000077500000000000000000000000001365354716400157405ustar00rootroot00000000000000gin-1.6.3/testdata/template/hello.tmpl000066400000000000000000000000321365354716400177340ustar00rootroot00000000000000

Hello {[{.name}]}

gin-1.6.3/testdata/template/raw.tmpl000066400000000000000000000000401365354716400174210ustar00rootroot00000000000000Date: {[{.now | formatAsDate}]} gin-1.6.3/tree.go000066400000000000000000000412401365354716400136030ustar00rootroot00000000000000// Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin import ( "net/url" "strings" "unicode" ) // Param is a single URL parameter, consisting of a key and a value. type Param struct { Key string Value string } // Params is a Param-slice, as returned by the router. // The slice is ordered, the first URL parameter is also the first slice value. // It is therefore safe to read values by the index. type Params []Param // Get returns the value of the first Param which key matches the given name. // If no matching Param is found, an empty string is returned. func (ps Params) Get(name string) (string, bool) { for _, entry := range ps { if entry.Key == name { return entry.Value, true } } return "", false } // ByName returns the value of the first Param which key matches the given name. // If no matching Param is found, an empty string is returned. func (ps Params) ByName(name string) (va string) { va, _ = ps.Get(name) return } type methodTree struct { method string root *node } type methodTrees []methodTree func (trees methodTrees) get(method string) *node { for _, tree := range trees { if tree.method == method { return tree.root } } return nil } func min(a, b int) int { if a <= b { return a } return b } func longestCommonPrefix(a, b string) int { i := 0 max := min(len(a), len(b)) for i < max && a[i] == b[i] { i++ } return i } func countParams(path string) uint8 { var n uint for i := 0; i < len(path); i++ { if path[i] == ':' || path[i] == '*' { n++ } } if n >= 255 { return 255 } return uint8(n) } type nodeType uint8 const ( static nodeType = iota // default root param catchAll ) type node struct { path string indices string children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool fullPath string } // increments priority of the given child and reorders if necessary. func (n *node) incrementChildPrio(pos int) int { cs := n.children cs[pos].priority++ prio := cs[pos].priority // Adjust position (move to front) newPos := pos for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- { // Swap node positions cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1] } // build new index char string if newPos != pos { n.indices = n.indices[:newPos] + // unchanged prefix, might be empty n.indices[pos:pos+1] + // the index char we move n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' } return newPos } // addRoute adds a node with the given handle to the path. // Not concurrency-safe! func (n *node) addRoute(path string, handlers HandlersChain) { fullPath := path n.priority++ numParams := countParams(path) // Empty tree if len(n.path) == 0 && len(n.children) == 0 { n.insertChild(numParams, path, fullPath, handlers) n.nType = root return } parentFullPathIndex := 0 walk: for { // Update maxParams of the current node if numParams > n.maxParams { n.maxParams = numParams } // Find the longest common prefix. // This also implies that the common prefix contains no ':' or '*' // since the existing key can't contain those chars. i := longestCommonPrefix(path, n.path) // Split edge if i < len(n.path) { child := node{ path: n.path[i:], wildChild: n.wildChild, indices: n.indices, children: n.children, handlers: n.handlers, priority: n.priority - 1, fullPath: n.fullPath, } // Update maxParams (max of all children) for _, v := range child.children { if v.maxParams > child.maxParams { child.maxParams = v.maxParams } } n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 n.indices = string([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false n.fullPath = fullPath[:parentFullPathIndex+i] } // Make new node a child of this node if i < len(path) { path = path[i:] if n.wildChild { parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ // Update maxParams of the child node if numParams > n.maxParams { n.maxParams = numParams } numParams-- // Check if the wildcard matches if len(path) >= len(n.path) && n.path == path[:len(n.path)] { // check for longer wildcard, e.g. :name and :names if len(n.path) >= len(path) || path[len(n.path)] == '/' { continue walk } } pathSeg := path if n.nType != catchAll { pathSeg = strings.SplitN(path, "/", 2)[0] } prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path panic("'" + pathSeg + "' in new path '" + fullPath + "' conflicts with existing wildcard '" + n.path + "' in existing prefix '" + prefix + "'") } c := path[0] // slash after param if n.nType == param && c == '/' && len(n.children) == 1 { parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ continue walk } // Check if a child with the next path byte exists for i, max := 0, len(n.indices); i < max; i++ { if c == n.indices[i] { parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) n = n.children[i] continue walk } } // Otherwise insert it if c != ':' && c != '*' { // []byte for proper unicode char conversion, see #65 n.indices += string([]byte{c}) child := &node{ maxParams: numParams, fullPath: fullPath, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } n.insertChild(numParams, path, fullPath, handlers) return } // Otherwise and handle to current node if n.handlers != nil { panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers return } } // Search for a wildcard segment and check the name for invalid characters. // Returns -1 as index, if no wildcard war found. func findWildcard(path string) (wildcard string, i int, valid bool) { // Find start for start, c := range []byte(path) { // A wildcard starts with ':' (param) or '*' (catch-all) if c != ':' && c != '*' { continue } // Find end and check for invalid characters valid = true for end, c := range []byte(path[start+1:]) { switch c { case '/': return path[start : start+1+end], start, valid case ':', '*': valid = false } } return path[start:], start, valid } return "", -1, false } func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { for numParams > 0 { // Find prefix until first wildcard wildcard, i, valid := findWildcard(path) if i < 0 { // No wildcard found break } // The wildcard name must not contain ':' and '*' if !valid { panic("only one wildcard per path segment is allowed, has: '" + wildcard + "' in path '" + fullPath + "'") } // check if the wildcard has a name if len(wildcard) < 2 { panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } // Check if this node has existing children which would be // unreachable if we insert the wildcard here if len(n.children) > 0 { panic("wildcard segment '" + wildcard + "' conflicts with existing children in path '" + fullPath + "'") } if wildcard[0] == ':' { // param if i > 0 { // Insert prefix before the current wildcard n.path = path[:i] path = path[i:] } n.wildChild = true child := &node{ nType: param, path: wildcard, maxParams: numParams, fullPath: fullPath, } n.children = []*node{child} n = child n.priority++ numParams-- // if the path doesn't end with the wildcard, then there // will be another non-wildcard subpath starting with '/' if len(wildcard) < len(path) { path = path[len(wildcard):] child := &node{ maxParams: numParams, priority: 1, fullPath: fullPath, } n.children = []*node{child} n = child continue } // Otherwise we're done. Insert the handle in the new leaf n.handlers = handlers return } // catchAll if i+len(wildcard) != len(path) || numParams > 1 { panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") } if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") } // currently fixed width 1 for '/' i-- if path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") } n.path = path[:i] // First node: catchAll node with empty path child := &node{ wildChild: true, nType: catchAll, maxParams: 1, fullPath: fullPath, } // update maxParams of the parent node if n.maxParams < 1 { n.maxParams = 1 } n.children = []*node{child} n.indices = string('/') n = child n.priority++ // second node: node holding the variable child = &node{ path: path[i:], nType: catchAll, maxParams: 1, handlers: handlers, priority: 1, fullPath: fullPath, } n.children = []*node{child} return } // If no wildcard was found, simply insert the path and handle n.path = path n.handlers = handlers n.fullPath = fullPath } // nodeValue holds return values of (*Node).getValue method type nodeValue struct { handlers HandlersChain params Params tsr bool fullPath string } // getValue returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) { value.params = po walk: // Outer loop for walking the tree for { prefix := n.path if path == prefix { // We should have reached the node containing the handle. // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath return } if path == "/" && n.wildChild && n.nType != root { value.tsr = true return } // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation indices := n.indices for i, max := 0, len(indices); i < max; i++ { if indices[i] == '/' { n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) return } } return } if len(path) > len(prefix) && path[:len(prefix)] == prefix { path = path[len(prefix):] // If this node does not have a wildcard (param or catchAll) // child, we can just look up the next child node and continue // to walk down the tree if !n.wildChild { c := path[0] indices := n.indices for i, max := 0, len(indices); i < max; i++ { if c == indices[i] { n = n.children[i] continue walk } } // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. value.tsr = path == "/" && n.handlers != nil return } // handle wildcard child n = n.children[0] switch n.nType { case param: // find param end (either '/' or path end) end := 0 for end < len(path) && path[end] != '/' { end++ } // save param value if cap(value.params) < int(n.maxParams) { value.params = make(Params, 0, n.maxParams) } i := len(value.params) value.params = value.params[:i+1] // expand slice within preallocated capacity value.params[i].Key = n.path[1:] val := path[:end] if unescape { var err error if value.params[i].Value, err = url.QueryUnescape(val); err != nil { value.params[i].Value = val // fallback, in case of error } } else { value.params[i].Value = val } // we need to go deeper! if end < len(path) { if len(n.children) > 0 { path = path[end:] n = n.children[0] continue walk } // ... but we can't value.tsr = len(path) == end+1 return } if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath return } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] value.tsr = n.path == "/" && n.handlers != nil } return case catchAll: // save param value if cap(value.params) < int(n.maxParams) { value.params = make(Params, 0, n.maxParams) } i := len(value.params) value.params = value.params[:i+1] // expand slice within preallocated capacity value.params[i].Key = n.path[2:] if unescape { var err error if value.params[i].Value, err = url.QueryUnescape(path); err != nil { value.params[i].Value = path // fallback, in case of error } } else { value.params[i].Value = path } value.handlers = n.handlers value.fullPath = n.fullPath return default: panic("invalid node type") } } // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = (path == "/") || (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && path == prefix[:len(prefix)-1] && n.handlers != nil) return } } // findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler. // It can optionally also fix trailing slashes. // It returns the case-corrected path and a bool indicating whether the lookup // was successful. func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory // Outer loop for walking the tree for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) { path = path[len(n.path):] ciPath = append(ciPath, n.path...) if len(path) == 0 { // We should have reached the node containing the handle. // Check if this node has a handle registered. if n.handlers != nil { return ciPath, true } // No handle found. // Try to fix the path by adding a trailing slash if fixTrailingSlash { for i := 0; i < len(n.indices); i++ { if n.indices[i] == '/' { n = n.children[i] if (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) { return append(ciPath, '/'), true } return } } } return } // If this node does not have a wildcard (param or catchAll) child, // we can just look up the next child node and continue to walk down // the tree if !n.wildChild { r := unicode.ToLower(rune(path[0])) for i, index := range n.indices { // must use recursive approach since both index and // ToLower(index) could exist. We must check both. if r == unicode.ToLower(index) { out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) if found { return append(ciPath, out...), true } } } // Nothing found. We can recommend to redirect to the same URL // without a trailing slash if a leaf exists for that path found = fixTrailingSlash && path == "/" && n.handlers != nil return } n = n.children[0] switch n.nType { case param: // Find param end (either '/' or path end) end := 0 for end < len(path) && path[end] != '/' { end++ } // add param value to case insensitive path ciPath = append(ciPath, path[:end]...) // we need to go deeper! if end < len(path) { if len(n.children) > 0 { path = path[end:] n = n.children[0] continue } // ... but we can't if fixTrailingSlash && len(path) == end+1 { return ciPath, true } return } if n.handlers != nil { return ciPath, true } if fixTrailingSlash && len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists n = n.children[0] if n.path == "/" && n.handlers != nil { return append(ciPath, '/'), true } } return case catchAll: return append(ciPath, path...), true default: panic("invalid node type") } } // Nothing found. // Try to fix the path by adding / removing a trailing slash if fixTrailingSlash { if path == "/" { return ciPath, true } if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && strings.EqualFold(path, n.path[:len(path)]) && n.handlers != nil { return append(ciPath, n.path...), true } } return } gin-1.6.3/tree_test.go000066400000000000000000000433301365354716400146440ustar00rootroot00000000000000// Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE package gin import ( "fmt" "reflect" "regexp" "strings" "testing" ) // Used as a workaround since we can't compare functions or their addresses var fakeHandlerValue string func fakeHandler(val string) HandlersChain { return HandlersChain{func(c *Context) { fakeHandlerValue = val }} } type testRequests []struct { path string nilHandler bool route string ps Params } func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { unescape := false if len(unescapes) >= 1 { unescape = unescapes[0] } for _, request := range requests { value := tree.getValue(request.path, nil, unescape) if value.handlers == nil { if !request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) } } else if request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) } else { value.handlers[0](nil) if fakeHandlerValue != request.route { t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) } } if !reflect.DeepEqual(value.params, request.ps) { t.Errorf("Params mismatch for route '%s'", request.path) } } } func checkPriorities(t *testing.T, n *node) uint32 { var prio uint32 for i := range n.children { prio += checkPriorities(t, n.children[i]) } if n.handlers != nil { prio++ } if n.priority != prio { t.Errorf( "priority mismatch for node '%s': is %d, should be %d", n.path, n.priority, prio, ) } return prio } func checkMaxParams(t *testing.T, n *node) uint8 { var maxParams uint8 for i := range n.children { params := checkMaxParams(t, n.children[i]) if params > maxParams { maxParams = params } } if n.nType > root && !n.wildChild { maxParams++ } if n.maxParams != maxParams { t.Errorf( "maxParams mismatch for node '%s': is %d, should be %d", n.path, n.maxParams, maxParams, ) } return maxParams } func TestCountParams(t *testing.T) { if countParams("/path/:param1/static/*catch-all") != 2 { t.Fail() } if countParams(strings.Repeat("/:param", 256)) != 255 { t.Fail() } } func TestTreeAddAndGet(t *testing.T) { tree := &node{} routes := [...]string{ "/hi", "/contact", "/co", "/c", "/a", "/ab", "/doc/", "/doc/go_faq.html", "/doc/go1.html", "/α", "/β", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) } checkRequests(t, tree, testRequests{ {"/a", false, "/a", nil}, {"/", true, "", nil}, {"/hi", false, "/hi", nil}, {"/contact", false, "/contact", nil}, {"/co", false, "/co", nil}, {"/con", true, "", nil}, // key mismatch {"/cona", true, "", nil}, // key mismatch {"/no", true, "", nil}, // no matching child {"/ab", false, "/ab", nil}, {"/α", false, "/α", nil}, {"/β", false, "/β", nil}, }) checkPriorities(t, tree) checkMaxParams(t, tree) } func TestTreeWildcard(t *testing.T) { tree := &node{} routes := [...]string{ "/", "/cmd/:tool/:sub", "/cmd/:tool/", "/src/*filepath", "/search/", "/search/:query", "/user_:name", "/user_:name/about", "/files/:dir/*filepath", "/doc/", "/doc/go_faq.html", "/doc/go1.html", "/info/:user/public", "/info/:user/project/:project", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) } checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}}, {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}}, {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}}, {"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, {"/search/", false, "/search/", nil}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}}, {"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}}, {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}}, {"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, }) checkPriorities(t, tree) checkMaxParams(t, tree) } func TestUnescapeParameters(t *testing.T) { tree := &node{} routes := [...]string{ "/", "/cmd/:tool/:sub", "/cmd/:tool/", "/src/*filepath", "/search/:query", "/files/:dir/*filepath", "/info/:user/project/:project", "/info/:user", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) } unescape := true checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}}, {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file test.png"}}}, {"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file++++%%%%test.png"}}}, {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file/test.png"}}}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng in ünìcodé"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{Key: "user", Value: "slash/gordon"}}}, {"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash/gordon"}, Param{Key: "project", Value: "Project #1"}}}, {"/info/slash%%%%", false, "/info/:user", Params{Param{Key: "user", Value: "slash%%%%"}}}, {"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash%%%%2Fgordon"}, Param{Key: "project", Value: "Project%%%%20%231"}}}, }, unescape) checkPriorities(t, tree) checkMaxParams(t, tree) } func catchPanic(testFunc func()) (recv interface{}) { defer func() { recv = recover() }() testFunc() return } type testRoute struct { path string conflict bool } func testRoutes(t *testing.T, routes []testRoute) { tree := &node{} for _, route := range routes { recv := catchPanic(func() { tree.addRoute(route.path, nil) }) if route.conflict { if recv == nil { t.Errorf("no panic for conflicting route '%s'", route.path) } } else if recv != nil { t.Errorf("unexpected panic for route '%s': %v", route.path, recv) } } } func TestTreeWildcardConflict(t *testing.T) { routes := []testRoute{ {"/cmd/:tool/:sub", false}, {"/cmd/vet", true}, {"/src/*filepath", false}, {"/src/*filepathx", true}, {"/src/", true}, {"/src1/", false}, {"/src1/*filepath", true}, {"/src2*filepath", true}, {"/search/:query", false}, {"/search/invalid", true}, {"/user_:name", false}, {"/user_x", true}, {"/user_:name", false}, {"/id:id", false}, {"/id/:id", true}, } testRoutes(t, routes) } func TestTreeChildConflict(t *testing.T) { routes := []testRoute{ {"/cmd/vet", false}, {"/cmd/:tool/:sub", true}, {"/src/AUTHORS", false}, {"/src/*filepath", true}, {"/user_x", false}, {"/user_:name", true}, {"/id/:id", false}, {"/id:id", true}, {"/:id", true}, {"/*filepath", true}, } testRoutes(t, routes) } func TestTreeDupliatePath(t *testing.T) { tree := &node{} routes := [...]string{ "/", "/doc/", "/src/*filepath", "/search/:query", "/user_:name", } for _, route := range routes { recv := catchPanic(func() { tree.addRoute(route, fakeHandler(route)) }) if recv != nil { t.Fatalf("panic inserting route '%s': %v", route, recv) } // Add again recv = catchPanic(func() { tree.addRoute(route, nil) }) if recv == nil { t.Fatalf("no panic while inserting duplicate route '%s", route) } } checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}}, }) } func TestEmptyWildcardName(t *testing.T) { tree := &node{} routes := [...]string{ "/user:", "/user:/", "/cmd/:/", "/src/*", } for _, route := range routes { recv := catchPanic(func() { tree.addRoute(route, nil) }) if recv == nil { t.Fatalf("no panic while inserting route with empty wildcard name '%s", route) } } } func TestTreeCatchAllConflict(t *testing.T) { routes := []testRoute{ {"/src/*filepath/x", true}, {"/src2/", false}, {"/src2/*filepath/x", true}, } testRoutes(t, routes) } func TestTreeCatchAllConflictRoot(t *testing.T) { routes := []testRoute{ {"/", false}, {"/*filepath", true}, } testRoutes(t, routes) } func TestTreeCatchMaxParams(t *testing.T) { tree := &node{} var route = "/cmd/*filepath" tree.addRoute(route, fakeHandler(route)) checkMaxParams(t, tree) } func TestTreeDoubleWildcard(t *testing.T) { const panicMsg = "only one wildcard per path segment is allowed" routes := [...]string{ "/:foo:bar", "/:foo:bar/", "/:foo*bar", } for _, route := range routes { tree := &node{} recv := catchPanic(func() { tree.addRoute(route, nil) }) if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) { t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv) } } } /*func TestTreeDuplicateWildcard(t *testing.T) { tree := &node{} routes := [...]string{ "/:id/:name/:id", } for _, route := range routes { ... } }*/ func TestTreeTrailingSlashRedirect(t *testing.T) { tree := &node{} routes := [...]string{ "/hi", "/b/", "/search/:query", "/cmd/:tool/", "/src/*filepath", "/x", "/x/y", "/y/", "/y/z", "/0/:id", "/0/:id/1", "/1/:id/", "/1/:id/2", "/aa", "/a/", "/admin", "/admin/:category", "/admin/:category/:page", "/doc", "/doc/go_faq.html", "/doc/go1.html", "/no/a", "/no/b", "/api/hello/:name", } for _, route := range routes { recv := catchPanic(func() { tree.addRoute(route, fakeHandler(route)) }) if recv != nil { t.Fatalf("panic inserting route '%s': %v", route, recv) } } tsrRoutes := [...]string{ "/hi/", "/b", "/search/gopher/", "/cmd/vet", "/src", "/x/", "/y", "/0/go/", "/1/go", "/a", "/admin/", "/admin/config/", "/admin/config/permissions/", "/doc/", } for _, route := range tsrRoutes { value := tree.getValue(route, nil, false) if value.handlers != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !value.tsr { t.Errorf("expected TSR recommendation for route '%s'", route) } } noTsrRoutes := [...]string{ "/", "/no", "/no/", "/_", "/_/", "/api/world/abc", } for _, route := range noTsrRoutes { value := tree.getValue(route, nil, false) if value.handlers != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if value.tsr { t.Errorf("expected no TSR recommendation for route '%s'", route) } } } func TestTreeRootTrailingSlashRedirect(t *testing.T) { tree := &node{} recv := catchPanic(func() { tree.addRoute("/:test", fakeHandler("/:test")) }) if recv != nil { t.Fatalf("panic inserting test route: %v", recv) } value := tree.getValue("/", nil, false) if value.handlers != nil { t.Fatalf("non-nil handler") } else if value.tsr { t.Errorf("expected no TSR recommendation") } } func TestTreeFindCaseInsensitivePath(t *testing.T) { tree := &node{} routes := [...]string{ "/hi", "/b/", "/ABC/", "/search/:query", "/cmd/:tool/", "/src/*filepath", "/x", "/x/y", "/y/", "/y/z", "/0/:id", "/0/:id/1", "/1/:id/", "/1/:id/2", "/aa", "/a/", "/doc", "/doc/go_faq.html", "/doc/go1.html", "/doc/go/away", "/no/a", "/no/b", } for _, route := range routes { recv := catchPanic(func() { tree.addRoute(route, fakeHandler(route)) }) if recv != nil { t.Fatalf("panic inserting route '%s': %v", route, recv) } } // Check out == in for all registered routes // With fixTrailingSlash = true for _, route := range routes { out, found := tree.findCaseInsensitivePath(route, true) if !found { t.Errorf("Route '%s' not found!", route) } else if string(out) != route { t.Errorf("Wrong result for route '%s': %s", route, string(out)) } } // With fixTrailingSlash = false for _, route := range routes { out, found := tree.findCaseInsensitivePath(route, false) if !found { t.Errorf("Route '%s' not found!", route) } else if string(out) != route { t.Errorf("Wrong result for route '%s': %s", route, string(out)) } } tests := []struct { in string out string found bool slash bool }{ {"/HI", "/hi", true, false}, {"/HI/", "/hi", true, true}, {"/B", "/b/", true, true}, {"/B/", "/b/", true, false}, {"/abc", "/ABC/", true, true}, {"/abc/", "/ABC/", true, false}, {"/aBc", "/ABC/", true, true}, {"/aBc/", "/ABC/", true, false}, {"/abC", "/ABC/", true, true}, {"/abC/", "/ABC/", true, false}, {"/SEARCH/QUERY", "/search/QUERY", true, false}, {"/SEARCH/QUERY/", "/search/QUERY", true, true}, {"/CMD/TOOL/", "/cmd/TOOL/", true, false}, {"/CMD/TOOL", "/cmd/TOOL/", true, true}, {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false}, {"/x/Y", "/x/y", true, false}, {"/x/Y/", "/x/y", true, true}, {"/X/y", "/x/y", true, false}, {"/X/y/", "/x/y", true, true}, {"/X/Y", "/x/y", true, false}, {"/X/Y/", "/x/y", true, true}, {"/Y/", "/y/", true, false}, {"/Y", "/y/", true, true}, {"/Y/z", "/y/z", true, false}, {"/Y/z/", "/y/z", true, true}, {"/Y/Z", "/y/z", true, false}, {"/Y/Z/", "/y/z", true, true}, {"/y/Z", "/y/z", true, false}, {"/y/Z/", "/y/z", true, true}, {"/Aa", "/aa", true, false}, {"/Aa/", "/aa", true, true}, {"/AA", "/aa", true, false}, {"/AA/", "/aa", true, true}, {"/aA", "/aa", true, false}, {"/aA/", "/aa", true, true}, {"/A/", "/a/", true, false}, {"/A", "/a/", true, true}, {"/DOC", "/doc", true, false}, {"/DOC/", "/doc", true, true}, {"/NO", "", false, true}, {"/DOC/GO", "", false, true}, } // With fixTrailingSlash = true for _, test := range tests { out, found := tree.findCaseInsensitivePath(test.in, true) if found != test.found || (found && (string(out) != test.out)) { t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", test.in, string(out), found, test.out, test.found) return } } // With fixTrailingSlash = false for _, test := range tests { out, found := tree.findCaseInsensitivePath(test.in, false) if test.slash { if found { // test needs a trailingSlash fix. It must not be found! t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out)) } } else { if found != test.found || (found && (string(out) != test.out)) { t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", test.in, string(out), found, test.out, test.found) return } } } } func TestTreeInvalidNodeType(t *testing.T) { const panicMsg = "invalid node type" tree := &node{} tree.addRoute("/", fakeHandler("/")) tree.addRoute("/:page", fakeHandler("/:page")) // set invalid node type tree.children[0].nType = 42 // normal lookup recv := catchPanic(func() { tree.getValue("/test", nil, false) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) } // case-insensitive lookup recv = catchPanic(func() { tree.findCaseInsensitivePath("/test", true) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) } } func TestTreeWildcardConflictEx(t *testing.T) { conflicts := [...]struct { route string segPath string existPath string existSegPath string }{ {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`}, {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`}, {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`}, {"/conxxx", "xxx", `/con:tact`, `:tact`}, {"/conooo/xxx", "ooo", `/con:tact`, `:tact`}, } for _, conflict := range conflicts { // I have to re-create a 'tree', because the 'tree' will be // in an inconsistent state when the loop recovers from the // panic which threw by 'addRoute' function. tree := &node{} routes := [...]string{ "/con:tact", "/who/are/*you", "/who/foo/hello", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) } recv := catchPanic(func() { tree.addRoute(conflict.route, fakeHandler(conflict.route)) }) if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { t.Fatalf("invalid wildcard conflict error (%v)", recv) } } } gin-1.6.3/utils.go000066400000000000000000000064741365354716400140160ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "encoding/xml" "net/http" "os" "path" "reflect" "runtime" "strings" ) // BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" // Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val interface{}) HandlerFunc { value := reflect.ValueOf(val) if value.Kind() == reflect.Ptr { panic(`Bind struct can not be a pointer. Example: Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{}) `) } typ := value.Type() return func(c *Context) { obj := reflect.New(typ).Interface() if c.Bind(obj) == nil { c.Set(BindKey, obj) } } } // WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware. func WrapF(f http.HandlerFunc) HandlerFunc { return func(c *Context) { f(c.Writer, c.Request) } } // WrapH is a helper function for wrapping http.Handler and returns a Gin middleware. func WrapH(h http.Handler) HandlerFunc { return func(c *Context) { h.ServeHTTP(c.Writer, c.Request) } } // H is a shortcut for map[string]interface{} type H map[string]interface{} // MarshalXML allows type H to be used with xml.Marshal. func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{ Space: "", Local: "map", } if err := e.EncodeToken(start); err != nil { return err } for key, value := range h { elem := xml.StartElement{ Name: xml.Name{Space: "", Local: key}, Attr: []xml.Attr{}, } if err := e.EncodeElement(value, elem); err != nil { return err } } return e.EncodeToken(xml.EndElement{Name: start.Name}) } func assert1(guard bool, text string) { if !guard { panic(text) } } func filterFlags(content string) string { for i, char := range content { if char == ' ' || char == ';' { return content[:i] } } return content } func chooseData(custom, wildcard interface{}) interface{} { if custom == nil { if wildcard == nil { panic("negotiation config is invalid") } return wildcard } return custom } func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { out = append(out, part) } } return out } func lastChar(str string) uint8 { if str == "" { panic("The length of the string can't be 0") } return str[len(str)-1] } func nameOfFunction(f interface{}) string { return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() } func joinPaths(absolutePath, relativePath string) string { if relativePath == "" { return absolutePath } finalPath := path.Join(absolutePath, relativePath) appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/' if appendSlash { return finalPath + "/" } return finalPath } func resolveAddress(addr []string) string { switch len(addr) { case 0: if port := os.Getenv("PORT"); port != "" { debugPrint("Environment variable PORT=\"%s\"", port) return ":" + port } debugPrint("Environment variable PORT is undefined. Using port :8080 by default") return ":8080" case 1: return addr[0] default: panic("too many parameters") } } gin-1.6.3/utils_test.go000066400000000000000000000071171365354716400150500ustar00rootroot00000000000000// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "bytes" "encoding/xml" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" ) func init() { SetMode(TestMode) } type testStruct struct { T *testing.T } func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { assert.Equal(t.T, "POST", req.Method) assert.Equal(t.T, "/path", req.URL.Path) w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") } func TestWrap(t *testing.T) { router := New() router.POST("/path", WrapH(&testStruct{t})) router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { assert.Equal(t, "GET", req.Method) assert.Equal(t, "/path2", req.URL.Path) w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) w := performRequest(router, "POST", "/path") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) w = performRequest(router, "GET", "/path2") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } func TestLastChar(t *testing.T) { assert.Equal(t, uint8('a'), lastChar("hola")) assert.Equal(t, uint8('s'), lastChar("adios")) assert.Panics(t, func() { lastChar("") }) } func TestParseAccept(t *testing.T) { parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") assert.Len(t, parts, 4) assert.Equal(t, "text/html", parts[0]) assert.Equal(t, "application/xhtml+xml", parts[1]) assert.Equal(t, "application/xml", parts[2]) assert.Equal(t, "*/*", parts[3]) } func TestChooseData(t *testing.T) { A := "a" B := "b" assert.Equal(t, A, chooseData(A, B)) assert.Equal(t, B, chooseData(nil, B)) assert.Panics(t, func() { chooseData(nil, nil) }) } func TestFilterFlags(t *testing.T) { result := filterFlags("text/html ") assert.Equal(t, "text/html", result) result = filterFlags("text/html;") assert.Equal(t, "text/html", result) } func TestFunctionName(t *testing.T) { assert.Regexp(t, `^(.*/vendor/)?github.com/gin-gonic/gin.somefunction$`, nameOfFunction(somefunction)) } func somefunction() { // this empty function is used by TestFunctionName() } func TestJoinPaths(t *testing.T) { assert.Equal(t, "", joinPaths("", "")) assert.Equal(t, "/", joinPaths("", "/")) assert.Equal(t, "/a", joinPaths("/a", "")) assert.Equal(t, "/a/", joinPaths("/a/", "")) assert.Equal(t, "/a/", joinPaths("/a/", "/")) assert.Equal(t, "/a/", joinPaths("/a", "/")) assert.Equal(t, "/a/hola", joinPaths("/a", "/hola")) assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola")) assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/")) assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//")) } type bindTestStruct struct { Foo string `form:"foo" binding:"required"` Bar int `form:"bar" binding:"min=4"` } func TestBindMiddleware(t *testing.T) { var value *bindTestStruct var called bool router := New() router.GET("/", Bind(bindTestStruct{}), func(c *Context) { called = true value = c.MustGet(BindKey).(*bindTestStruct) }) performRequest(router, "GET", "/?foo=hola&bar=10") assert.True(t, called) assert.Equal(t, "hola", value.Foo) assert.Equal(t, 10, value.Bar) called = false performRequest(router, "GET", "/?foo=hola&bar=1") assert.False(t, called) assert.Panics(t, func() { Bind(&bindTestStruct{}) }) } func TestMarshalXMLforH(t *testing.T) { h := H{ "": "test", } var b bytes.Buffer enc := xml.NewEncoder(&b) var x xml.StartElement e := h.MarshalXML(enc, x) assert.Error(t, e) } gin-1.6.3/version.go000066400000000000000000000003711365354716400143310ustar00rootroot00000000000000// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin // Version is the current gin framework's version. const Version = "v1.6.3"