gin-1.3.0/000077500000000000000000000000001333451471400122755ustar00rootroot00000000000000gin-1.3.0/.github/000077500000000000000000000000001333451471400136355ustar00rootroot00000000000000gin-1.3.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000004531333451471400163440ustar00rootroot00000000000000- 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. - gin version (or commit ref): - git version: - operating system: ## Description ## Screenshots gin-1.3.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000006261333451471400174420ustar00rootroot00000000000000- 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.3.0/.gitignore000066400000000000000000000000711333451471400142630ustar00rootroot00000000000000vendor/* !vendor/vendor.json coverage.out count.out test gin-1.3.0/.travis.yml000066400000000000000000000011511333451471400144040ustar00rootroot00000000000000language: go sudo: false go: - 1.6.x - 1.7.x - 1.8.x - 1.9.x - 1.10.x - master git: depth: 10 install: - make install go_import_path: github.com/gin-gonic/gin script: - make vet - make fmt-check - make embedmd - 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.3.0/AUTHORS.md000066400000000000000000000101631333451471400137450ustar00rootroot00000000000000List 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.3.0/BENCHMARKS.md000066400000000000000000001503551333451471400142450ustar00rootroot00000000000000 ## Benchmark System **VM HOST:** DigitalOcean **Machine:** 4 CPU, 8 GB RAM. Ubuntu 16.04.2 x64 **Date:** July 19th, 2017 **Go Version:** 1.8.3 linux/amd64 **Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark) ## Static Routes: 157 ``` Gin: 30512 Bytes HttpServeMux: 17344 Bytes Ace: 30080 Bytes Bear: 30472 Bytes Beego: 96408 Bytes Bone: 37904 Bytes Denco: 10464 Bytes Echo: 73680 Bytes GocraftWeb: 55720 Bytes Goji: 27200 Bytes Gojiv2: 104464 Bytes GoJsonRest: 136472 Bytes GoRestful: 914904 Bytes GorillaMux: 675568 Bytes HttpRouter: 21128 Bytes HttpTreeMux: 73448 Bytes Kocha: 115072 Bytes LARS: 30120 Bytes Macaron: 37984 Bytes Martini: 310832 Bytes Pat: 20464 Bytes Possum: 91328 Bytes R2router: 23712 Bytes Rivet: 23880 Bytes Tango: 28008 Bytes TigerTonic: 80368 Bytes Traffic: 626480 Bytes Vulcan: 369064 Bytes ``` ## GithubAPI Routes: 203 ``` Gin: 52672 Bytes Ace: 48992 Bytes Bear: 161592 Bytes Beego: 147992 Bytes Bone: 97728 Bytes Denco: 36440 Bytes Echo: 95672 Bytes GocraftWeb: 95640 Bytes Goji: 86088 Bytes Gojiv2: 144392 Bytes GoJsonRest: 134648 Bytes GoRestful: 1410760 Bytes GorillaMux: 1509488 Bytes HttpRouter: 37464 Bytes HttpTreeMux: 78800 Bytes Kocha: 785408 Bytes LARS: 49032 Bytes Macaron: 132712 Bytes Martini: 564352 Bytes Pat: 21200 Bytes Possum: 83888 Bytes R2router: 47104 Bytes Rivet: 42840 Bytes Tango: 54584 Bytes TigerTonic: 96384 Bytes Traffic: 1061920 Bytes Vulcan: 465296 Bytes ``` ## GPlusAPI Routes: 13 ``` Gin: 3968 Bytes Ace: 3600 Bytes Bear: 7112 Bytes Beego: 10048 Bytes Bone: 6480 Bytes Denco: 3256 Bytes Echo: 9000 Bytes GocraftWeb: 7496 Bytes Goji: 2912 Bytes Gojiv2: 7376 Bytes GoJsonRest: 11544 Bytes GoRestful: 88776 Bytes GorillaMux: 71488 Bytes HttpRouter: 2712 Bytes HttpTreeMux: 7440 Bytes Kocha: 128880 Bytes LARS: 3640 Bytes Macaron: 8656 Bytes Martini: 23936 Bytes Pat: 1856 Bytes Possum: 7248 Bytes R2router: 3928 Bytes Rivet: 3064 Bytes Tango: 4912 Bytes TigerTonic: 9408 Bytes Traffic: 49472 Bytes Vulcan: 25496 Bytes ``` ## ParseAPI Routes: 26 ``` Gin: 6928 Bytes Ace: 6592 Bytes Bear: 12320 Bytes Beego: 18960 Bytes Bone: 11024 Bytes Denco: 4184 Bytes Echo: 11168 Bytes GocraftWeb: 12800 Bytes Goji: 5232 Bytes Gojiv2: 14464 Bytes GoJsonRest: 14216 Bytes GoRestful: 127368 Bytes GorillaMux: 123016 Bytes HttpRouter: 4976 Bytes HttpTreeMux: 7848 Bytes Kocha: 181712 Bytes LARS: 6632 Bytes Macaron: 13648 Bytes Martini: 45952 Bytes Pat: 2560 Bytes Possum: 9200 Bytes R2router: 7056 Bytes Rivet: 5680 Bytes Tango: 8664 Bytes TigerTonic: 9840 Bytes Traffic: 93480 Bytes Vulcan: 44504 Bytes ``` ## Static Routes ``` BenchmarkGin_StaticAll 50000 34506 ns/op 0 B/op 0 allocs/op BenchmarkAce_StaticAll 30000 49657 ns/op 0 B/op 0 allocs/op BenchmarkHttpServeMux_StaticAll 2000 1183737 ns/op 96 B/op 8 allocs/op BenchmarkBeego_StaticAll 5000 412621 ns/op 57776 B/op 628 allocs/op BenchmarkBear_StaticAll 10000 149242 ns/op 20336 B/op 461 allocs/op BenchmarkBone_StaticAll 10000 118583 ns/op 0 B/op 0 allocs/op BenchmarkDenco_StaticAll 100000 13247 ns/op 0 B/op 0 allocs/op BenchmarkEcho_StaticAll 20000 79914 ns/op 5024 B/op 157 allocs/op BenchmarkGocraftWeb_StaticAll 10000 211823 ns/op 46440 B/op 785 allocs/op BenchmarkGoji_StaticAll 10000 109390 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_StaticAll 3000 415533 ns/op 145696 B/op 1099 allocs/op BenchmarkGoJsonRest_StaticAll 5000 364403 ns/op 51653 B/op 1727 allocs/op BenchmarkGoRestful_StaticAll 500 2578579 ns/op 314936 B/op 3144 allocs/op BenchmarkGorillaMux_StaticAll 500 2704856 ns/op 115648 B/op 1578 allocs/op BenchmarkHttpRouter_StaticAll 100000 18541 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_StaticAll 100000 22332 ns/op 0 B/op 0 allocs/op BenchmarkKocha_StaticAll 50000 31176 ns/op 0 B/op 0 allocs/op BenchmarkLARS_StaticAll 50000 40840 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_StaticAll 5000 517656 ns/op 120576 B/op 1413 allocs/op BenchmarkMartini_StaticAll 300 4462289 ns/op 125442 B/op 1717 allocs/op BenchmarkPat_StaticAll 500 2157275 ns/op 533904 B/op 11123 allocs/op BenchmarkPossum_StaticAll 10000 254701 ns/op 65312 B/op 471 allocs/op BenchmarkR2router_StaticAll 10000 133956 ns/op 22608 B/op 628 allocs/op BenchmarkRivet_StaticAll 30000 46812 ns/op 0 B/op 0 allocs/op BenchmarkTango_StaticAll 5000 390613 ns/op 39225 B/op 1256 allocs/op BenchmarkTigerTonic_StaticAll 20000 88060 ns/op 7504 B/op 157 allocs/op BenchmarkTraffic_StaticAll 500 2910236 ns/op 729736 B/op 14287 allocs/op BenchmarkVulcan_StaticAll 5000 277366 ns/op 15386 B/op 471 allocs/op ``` ## Micro Benchmarks ``` BenchmarkGin_Param 20000000 113 ns/op 0 B/op 0 allocs/op BenchmarkAce_Param 5000000 375 ns/op 32 B/op 1 allocs/op BenchmarkBear_Param 1000000 1709 ns/op 456 B/op 5 allocs/op BenchmarkBeego_Param 1000000 2484 ns/op 368 B/op 4 allocs/op BenchmarkBone_Param 1000000 2391 ns/op 688 B/op 5 allocs/op BenchmarkDenco_Param 10000000 240 ns/op 32 B/op 1 allocs/op BenchmarkEcho_Param 5000000 366 ns/op 32 B/op 1 allocs/op BenchmarkGocraftWeb_Param 1000000 2343 ns/op 648 B/op 8 allocs/op BenchmarkGoji_Param 1000000 1197 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Param 1000000 2771 ns/op 944 B/op 8 allocs/op BenchmarkGoJsonRest_Param 1000000 2993 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_Param 200000 8860 ns/op 2296 B/op 21 allocs/op BenchmarkGorillaMux_Param 500000 4461 ns/op 1056 B/op 11 allocs/op BenchmarkHttpRouter_Param 10000000 175 ns/op 32 B/op 1 allocs/op BenchmarkHttpTreeMux_Param 1000000 1167 ns/op 352 B/op 3 allocs/op BenchmarkKocha_Param 3000000 429 ns/op 56 B/op 3 allocs/op BenchmarkLARS_Param 10000000 134 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param 500000 4635 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_Param 200000 9933 ns/op 1072 B/op 10 allocs/op BenchmarkPat_Param 1000000 2929 ns/op 648 B/op 12 allocs/op BenchmarkPossum_Param 1000000 2503 ns/op 560 B/op 6 allocs/op BenchmarkR2router_Param 1000000 1507 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Param 5000000 297 ns/op 48 B/op 1 allocs/op BenchmarkTango_Param 1000000 1862 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_Param 500000 5660 ns/op 992 B/op 17 allocs/op BenchmarkTraffic_Param 200000 8408 ns/op 1960 B/op 21 allocs/op BenchmarkVulcan_Param 2000000 963 ns/op 98 B/op 3 allocs/op BenchmarkAce_Param5 2000000 740 ns/op 160 B/op 1 allocs/op BenchmarkBear_Param5 1000000 2777 ns/op 501 B/op 5 allocs/op BenchmarkBeego_Param5 1000000 3740 ns/op 368 B/op 4 allocs/op BenchmarkBone_Param5 1000000 2950 ns/op 736 B/op 5 allocs/op BenchmarkDenco_Param5 2000000 644 ns/op 160 B/op 1 allocs/op BenchmarkEcho_Param5 3000000 558 ns/op 32 B/op 1 allocs/op BenchmarkGin_Param5 10000000 198 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param5 500000 3870 ns/op 920 B/op 11 allocs/op BenchmarkGoji_Param5 1000000 1746 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Param5 1000000 3214 ns/op 1008 B/op 8 allocs/op BenchmarkGoJsonRest_Param5 500000 5509 ns/op 1097 B/op 16 allocs/op BenchmarkGoRestful_Param5 200000 11232 ns/op 2392 B/op 21 allocs/op BenchmarkGorillaMux_Param5 300000 7777 ns/op 1184 B/op 11 allocs/op BenchmarkHttpRouter_Param5 3000000 631 ns/op 160 B/op 1 allocs/op BenchmarkHttpTreeMux_Param5 1000000 2800 ns/op 576 B/op 6 allocs/op BenchmarkKocha_Param5 1000000 2053 ns/op 440 B/op 10 allocs/op BenchmarkLARS_Param5 10000000 232 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param5 500000 5888 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_Param5 200000 12807 ns/op 1232 B/op 11 allocs/op BenchmarkPat_Param5 300000 7320 ns/op 964 B/op 32 allocs/op BenchmarkPossum_Param5 1000000 2495 ns/op 560 B/op 6 allocs/op BenchmarkR2router_Param5 1000000 1844 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Param5 2000000 935 ns/op 240 B/op 1 allocs/op BenchmarkTango_Param5 1000000 2327 ns/op 360 B/op 8 allocs/op BenchmarkTigerTonic_Param5 100000 18514 ns/op 2551 B/op 43 allocs/op BenchmarkTraffic_Param5 200000 11997 ns/op 2248 B/op 25 allocs/op BenchmarkVulcan_Param5 1000000 1333 ns/op 98 B/op 3 allocs/op BenchmarkAce_Param20 1000000 2031 ns/op 640 B/op 1 allocs/op BenchmarkBear_Param20 200000 7285 ns/op 1664 B/op 5 allocs/op BenchmarkBeego_Param20 300000 6224 ns/op 368 B/op 4 allocs/op BenchmarkBone_Param20 200000 8023 ns/op 1903 B/op 5 allocs/op BenchmarkDenco_Param20 1000000 2262 ns/op 640 B/op 1 allocs/op BenchmarkEcho_Param20 1000000 1387 ns/op 32 B/op 1 allocs/op BenchmarkGin_Param20 3000000 503 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param20 100000 14408 ns/op 3795 B/op 15 allocs/op BenchmarkGoji_Param20 500000 5272 ns/op 1247 B/op 2 allocs/op BenchmarkGojiv2_Param20 1000000 4163 ns/op 1248 B/op 8 allocs/op BenchmarkGoJsonRest_Param20 100000 17866 ns/op 4485 B/op 20 allocs/op BenchmarkGoRestful_Param20 100000 21022 ns/op 4724 B/op 23 allocs/op BenchmarkGorillaMux_Param20 100000 17055 ns/op 3547 B/op 13 allocs/op BenchmarkHttpRouter_Param20 1000000 1748 ns/op 640 B/op 1 allocs/op BenchmarkHttpTreeMux_Param20 200000 12246 ns/op 3196 B/op 10 allocs/op BenchmarkKocha_Param20 300000 6861 ns/op 1808 B/op 27 allocs/op BenchmarkLARS_Param20 3000000 526 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Param20 100000 13069 ns/op 2906 B/op 12 allocs/op BenchmarkMartini_Param20 100000 23602 ns/op 3597 B/op 13 allocs/op BenchmarkPat_Param20 50000 32143 ns/op 4688 B/op 111 allocs/op BenchmarkPossum_Param20 1000000 2396 ns/op 560 B/op 6 allocs/op BenchmarkR2router_Param20 200000 8907 ns/op 2283 B/op 7 allocs/op BenchmarkRivet_Param20 1000000 3280 ns/op 1024 B/op 1 allocs/op BenchmarkTango_Param20 500000 4640 ns/op 856 B/op 8 allocs/op BenchmarkTigerTonic_Param20 20000 67581 ns/op 10532 B/op 138 allocs/op BenchmarkTraffic_Param20 50000 40313 ns/op 7941 B/op 45 allocs/op BenchmarkVulcan_Param20 1000000 2264 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParamWrite 3000000 532 ns/op 40 B/op 2 allocs/op BenchmarkBear_ParamWrite 1000000 1778 ns/op 456 B/op 5 allocs/op BenchmarkBeego_ParamWrite 1000000 2596 ns/op 376 B/op 5 allocs/op BenchmarkBone_ParamWrite 1000000 2519 ns/op 688 B/op 5 allocs/op BenchmarkDenco_ParamWrite 5000000 411 ns/op 32 B/op 1 allocs/op BenchmarkEcho_ParamWrite 2000000 718 ns/op 40 B/op 2 allocs/op BenchmarkGin_ParamWrite 5000000 283 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParamWrite 1000000 2561 ns/op 656 B/op 9 allocs/op BenchmarkGoji_ParamWrite 1000000 1378 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_ParamWrite 1000000 3128 ns/op 976 B/op 10 allocs/op BenchmarkGoJsonRest_ParamWrite 500000 4446 ns/op 1128 B/op 18 allocs/op BenchmarkGoRestful_ParamWrite 200000 10291 ns/op 2304 B/op 22 allocs/op BenchmarkGorillaMux_ParamWrite 500000 5153 ns/op 1064 B/op 12 allocs/op BenchmarkHttpRouter_ParamWrite 5000000 263 ns/op 32 B/op 1 allocs/op BenchmarkHttpTreeMux_ParamWrite 1000000 1351 ns/op 352 B/op 3 allocs/op BenchmarkKocha_ParamWrite 3000000 538 ns/op 56 B/op 3 allocs/op BenchmarkLARS_ParamWrite 5000000 316 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParamWrite 500000 5756 ns/op 1160 B/op 14 allocs/op BenchmarkMartini_ParamWrite 200000 13097 ns/op 1176 B/op 14 allocs/op BenchmarkPat_ParamWrite 500000 4954 ns/op 1072 B/op 17 allocs/op BenchmarkPossum_ParamWrite 1000000 2499 ns/op 560 B/op 6 allocs/op BenchmarkR2router_ParamWrite 1000000 1531 ns/op 432 B/op 5 allocs/op BenchmarkRivet_ParamWrite 3000000 570 ns/op 112 B/op 2 allocs/op BenchmarkTango_ParamWrite 2000000 957 ns/op 136 B/op 4 allocs/op BenchmarkTigerTonic_ParamWrite 200000 7025 ns/op 1424 B/op 23 allocs/op BenchmarkTraffic_ParamWrite 200000 10112 ns/op 2384 B/op 25 allocs/op BenchmarkVulcan_ParamWrite 1000000 1006 ns/op 98 B/op 3 allocs/op ``` ## GitHub ``` BenchmarkGin_GithubStatic 10000000 156 ns/op 0 B/op 0 allocs/op BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op BenchmarkBear_GithubStatic 2000000 893 ns/op 120 B/op 3 allocs/op BenchmarkBeego_GithubStatic 1000000 2491 ns/op 368 B/op 4 allocs/op BenchmarkBone_GithubStatic 50000 25300 ns/op 2880 B/op 60 allocs/op BenchmarkDenco_GithubStatic 20000000 76.0 ns/op 0 B/op 0 allocs/op BenchmarkEcho_GithubStatic 2000000 516 ns/op 32 B/op 1 allocs/op BenchmarkGocraftWeb_GithubStatic 1000000 1448 ns/op 296 B/op 5 allocs/op BenchmarkGoji_GithubStatic 3000000 496 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_GithubStatic 1000000 2941 ns/op 928 B/op 7 allocs/op BenchmarkGoRestful_GithubStatic 100000 27256 ns/op 3224 B/op 22 allocs/op BenchmarkGoJsonRest_GithubStatic 1000000 2196 ns/op 329 B/op 11 allocs/op BenchmarkGorillaMux_GithubStatic 50000 31617 ns/op 736 B/op 10 allocs/op BenchmarkHttpRouter_GithubStatic 20000000 88.4 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_GithubStatic 10000000 134 ns/op 0 B/op 0 allocs/op BenchmarkKocha_GithubStatic 20000000 113 ns/op 0 B/op 0 allocs/op BenchmarkLARS_GithubStatic 10000000 195 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubStatic 500000 3740 ns/op 768 B/op 9 allocs/op BenchmarkMartini_GithubStatic 50000 27673 ns/op 768 B/op 9 allocs/op BenchmarkPat_GithubStatic 100000 19470 ns/op 3648 B/op 76 allocs/op BenchmarkPossum_GithubStatic 1000000 1729 ns/op 416 B/op 3 allocs/op BenchmarkR2router_GithubStatic 2000000 879 ns/op 144 B/op 4 allocs/op BenchmarkRivet_GithubStatic 10000000 231 ns/op 0 B/op 0 allocs/op BenchmarkTango_GithubStatic 1000000 2325 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_GithubStatic 3000000 610 ns/op 48 B/op 1 allocs/op BenchmarkTraffic_GithubStatic 20000 62973 ns/op 18904 B/op 148 allocs/op BenchmarkVulcan_GithubStatic 1000000 1447 ns/op 98 B/op 3 allocs/op BenchmarkAce_GithubParam 2000000 686 ns/op 96 B/op 1 allocs/op BenchmarkBear_GithubParam 1000000 2155 ns/op 496 B/op 5 allocs/op BenchmarkBeego_GithubParam 1000000 2713 ns/op 368 B/op 4 allocs/op BenchmarkBone_GithubParam 100000 15088 ns/op 1760 B/op 18 allocs/op BenchmarkDenco_GithubParam 2000000 629 ns/op 128 B/op 1 allocs/op BenchmarkEcho_GithubParam 2000000 653 ns/op 32 B/op 1 allocs/op BenchmarkGin_GithubParam 5000000 255 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GithubParam 1000000 3145 ns/op 712 B/op 9 allocs/op BenchmarkGoji_GithubParam 1000000 1916 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GithubParam 1000000 3975 ns/op 1024 B/op 10 allocs/op BenchmarkGoJsonRest_GithubParam 300000 4134 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_GithubParam 50000 30782 ns/op 2360 B/op 21 allocs/op BenchmarkGorillaMux_GithubParam 100000 17148 ns/op 1088 B/op 11 allocs/op BenchmarkHttpRouter_GithubParam 3000000 523 ns/op 96 B/op 1 allocs/op BenchmarkHttpTreeMux_GithubParam 1000000 1671 ns/op 384 B/op 4 allocs/op BenchmarkKocha_GithubParam 1000000 1021 ns/op 128 B/op 5 allocs/op BenchmarkLARS_GithubParam 5000000 283 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubParam 500000 4270 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_GithubParam 100000 21728 ns/op 1152 B/op 11 allocs/op BenchmarkPat_GithubParam 200000 11208 ns/op 2464 B/op 48 allocs/op BenchmarkPossum_GithubParam 1000000 2334 ns/op 560 B/op 6 allocs/op BenchmarkR2router_GithubParam 1000000 1487 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GithubParam 2000000 782 ns/op 96 B/op 1 allocs/op BenchmarkTango_GithubParam 1000000 2653 ns/op 344 B/op 8 allocs/op BenchmarkTigerTonic_GithubParam 300000 14073 ns/op 1440 B/op 24 allocs/op BenchmarkTraffic_GithubParam 50000 29164 ns/op 5992 B/op 52 allocs/op BenchmarkVulcan_GithubParam 1000000 2529 ns/op 98 B/op 3 allocs/op BenchmarkAce_GithubAll 10000 134059 ns/op 13792 B/op 167 allocs/op BenchmarkBear_GithubAll 5000 534445 ns/op 86448 B/op 943 allocs/op BenchmarkBeego_GithubAll 3000 592444 ns/op 74705 B/op 812 allocs/op BenchmarkBone_GithubAll 200 6957308 ns/op 698784 B/op 8453 allocs/op BenchmarkDenco_GithubAll 10000 158819 ns/op 20224 B/op 167 allocs/op BenchmarkEcho_GithubAll 10000 154700 ns/op 6496 B/op 203 allocs/op BenchmarkGin_GithubAll 30000 48375 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GithubAll 3000 570806 ns/op 131656 B/op 1686 allocs/op BenchmarkGoji_GithubAll 2000 818034 ns/op 56112 B/op 334 allocs/op BenchmarkGojiv2_GithubAll 2000 1213973 ns/op 274768 B/op 3712 allocs/op BenchmarkGoJsonRest_GithubAll 2000 785796 ns/op 134371 B/op 2737 allocs/op BenchmarkGoRestful_GithubAll 300 5238188 ns/op 689672 B/op 4519 allocs/op BenchmarkGorillaMux_GithubAll 100 10257726 ns/op 211840 B/op 2272 allocs/op BenchmarkHttpRouter_GithubAll 20000 105414 ns/op 13792 B/op 167 allocs/op BenchmarkHttpTreeMux_GithubAll 10000 319934 ns/op 65856 B/op 671 allocs/op BenchmarkKocha_GithubAll 10000 209442 ns/op 23304 B/op 843 allocs/op BenchmarkLARS_GithubAll 20000 62565 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GithubAll 2000 1161270 ns/op 204194 B/op 2000 allocs/op BenchmarkMartini_GithubAll 200 9991713 ns/op 226549 B/op 2325 allocs/op BenchmarkPat_GithubAll 200 5590793 ns/op 1499568 B/op 27435 allocs/op BenchmarkPossum_GithubAll 10000 319768 ns/op 84448 B/op 609 allocs/op BenchmarkR2router_GithubAll 10000 305134 ns/op 77328 B/op 979 allocs/op BenchmarkRivet_GithubAll 10000 132134 ns/op 16272 B/op 167 allocs/op BenchmarkTango_GithubAll 3000 552754 ns/op 63826 B/op 1618 allocs/op BenchmarkTigerTonic_GithubAll 1000 1439483 ns/op 239104 B/op 5374 allocs/op BenchmarkTraffic_GithubAll 100 11383067 ns/op 2659329 B/op 21848 allocs/op BenchmarkVulcan_GithubAll 5000 394253 ns/op 19894 B/op 609 allocs/op ``` ## Google+ ``` BenchmarkGin_GPlusStatic 10000000 183 ns/op 0 B/op 0 allocs/op BenchmarkAce_GPlusStatic 5000000 276 ns/op 0 B/op 0 allocs/op BenchmarkBear_GPlusStatic 2000000 652 ns/op 104 B/op 3 allocs/op BenchmarkBeego_GPlusStatic 1000000 2239 ns/op 368 B/op 4 allocs/op BenchmarkBone_GPlusStatic 5000000 380 ns/op 32 B/op 1 allocs/op BenchmarkDenco_GPlusStatic 30000000 45.8 ns/op 0 B/op 0 allocs/op BenchmarkEcho_GPlusStatic 5000000 338 ns/op 32 B/op 1 allocs/op BenchmarkGocraftWeb_GPlusStatic 1000000 1158 ns/op 280 B/op 5 allocs/op BenchmarkGoji_GPlusStatic 5000000 331 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_GPlusStatic 1000000 2106 ns/op 928 B/op 7 allocs/op BenchmarkGoJsonRest_GPlusStatic 1000000 1626 ns/op 329 B/op 11 allocs/op BenchmarkGoRestful_GPlusStatic 300000 7598 ns/op 1976 B/op 20 allocs/op BenchmarkGorillaMux_GPlusStatic 1000000 2629 ns/op 736 B/op 10 allocs/op BenchmarkHttpRouter_GPlusStatic 30000000 52.5 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_GPlusStatic 20000000 85.8 ns/op 0 B/op 0 allocs/op BenchmarkKocha_GPlusStatic 20000000 89.2 ns/op 0 B/op 0 allocs/op BenchmarkLARS_GPlusStatic 10000000 162 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusStatic 500000 3479 ns/op 768 B/op 9 allocs/op BenchmarkMartini_GPlusStatic 200000 9092 ns/op 768 B/op 9 allocs/op BenchmarkPat_GPlusStatic 3000000 493 ns/op 96 B/op 2 allocs/op BenchmarkPossum_GPlusStatic 1000000 1467 ns/op 416 B/op 3 allocs/op BenchmarkR2router_GPlusStatic 2000000 788 ns/op 144 B/op 4 allocs/op BenchmarkRivet_GPlusStatic 20000000 114 ns/op 0 B/op 0 allocs/op BenchmarkTango_GPlusStatic 1000000 1534 ns/op 200 B/op 8 allocs/op BenchmarkTigerTonic_GPlusStatic 5000000 282 ns/op 32 B/op 1 allocs/op BenchmarkTraffic_GPlusStatic 500000 3798 ns/op 1192 B/op 15 allocs/op BenchmarkVulcan_GPlusStatic 2000000 1125 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlusParam 3000000 528 ns/op 64 B/op 1 allocs/op BenchmarkBear_GPlusParam 1000000 1570 ns/op 480 B/op 5 allocs/op BenchmarkBeego_GPlusParam 1000000 2369 ns/op 368 B/op 4 allocs/op BenchmarkBone_GPlusParam 1000000 2028 ns/op 688 B/op 5 allocs/op BenchmarkDenco_GPlusParam 5000000 385 ns/op 64 B/op 1 allocs/op BenchmarkEcho_GPlusParam 3000000 441 ns/op 32 B/op 1 allocs/op BenchmarkGin_GPlusParam 10000000 174 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlusParam 1000000 2033 ns/op 648 B/op 8 allocs/op BenchmarkGoji_GPlusParam 1000000 1399 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GPlusParam 1000000 2641 ns/op 944 B/op 8 allocs/op BenchmarkGoJsonRest_GPlusParam 1000000 2824 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_GPlusParam 200000 8875 ns/op 2296 B/op 21 allocs/op BenchmarkGorillaMux_GPlusParam 200000 6291 ns/op 1056 B/op 11 allocs/op BenchmarkHttpRouter_GPlusParam 5000000 316 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_GPlusParam 1000000 1129 ns/op 352 B/op 3 allocs/op BenchmarkKocha_GPlusParam 3000000 538 ns/op 56 B/op 3 allocs/op BenchmarkLARS_GPlusParam 10000000 198 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusParam 500000 3554 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_GPlusParam 200000 9831 ns/op 1072 B/op 10 allocs/op BenchmarkPat_GPlusParam 1000000 2706 ns/op 688 B/op 12 allocs/op BenchmarkPossum_GPlusParam 1000000 2297 ns/op 560 B/op 6 allocs/op BenchmarkR2router_GPlusParam 1000000 1318 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GPlusParam 5000000 399 ns/op 48 B/op 1 allocs/op BenchmarkTango_GPlusParam 1000000 2070 ns/op 264 B/op 8 allocs/op BenchmarkTigerTonic_GPlusParam 500000 4853 ns/op 1056 B/op 17 allocs/op BenchmarkTraffic_GPlusParam 200000 8278 ns/op 1976 B/op 21 allocs/op BenchmarkVulcan_GPlusParam 1000000 1243 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlus2Params 3000000 549 ns/op 64 B/op 1 allocs/op BenchmarkBear_GPlus2Params 1000000 2112 ns/op 496 B/op 5 allocs/op BenchmarkBeego_GPlus2Params 500000 2750 ns/op 368 B/op 4 allocs/op BenchmarkBone_GPlus2Params 300000 7032 ns/op 1040 B/op 9 allocs/op BenchmarkDenco_GPlus2Params 3000000 502 ns/op 64 B/op 1 allocs/op BenchmarkEcho_GPlus2Params 3000000 641 ns/op 32 B/op 1 allocs/op BenchmarkGin_GPlus2Params 5000000 250 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlus2Params 1000000 2681 ns/op 712 B/op 9 allocs/op BenchmarkGoji_GPlus2Params 1000000 1926 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_GPlus2Params 500000 3996 ns/op 1024 B/op 11 allocs/op BenchmarkGoJsonRest_GPlus2Params 500000 3886 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_GPlus2Params 200000 10376 ns/op 2360 B/op 21 allocs/op BenchmarkGorillaMux_GPlus2Params 100000 14162 ns/op 1088 B/op 11 allocs/op BenchmarkHttpRouter_GPlus2Params 5000000 336 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_GPlus2Params 1000000 1523 ns/op 384 B/op 4 allocs/op BenchmarkKocha_GPlus2Params 2000000 970 ns/op 128 B/op 5 allocs/op BenchmarkLARS_GPlus2Params 5000000 238 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlus2Params 500000 4016 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_GPlus2Params 100000 21253 ns/op 1200 B/op 13 allocs/op BenchmarkPat_GPlus2Params 200000 8632 ns/op 2256 B/op 34 allocs/op BenchmarkPossum_GPlus2Params 1000000 2171 ns/op 560 B/op 6 allocs/op BenchmarkR2router_GPlus2Params 1000000 1340 ns/op 432 B/op 5 allocs/op BenchmarkRivet_GPlus2Params 3000000 557 ns/op 96 B/op 1 allocs/op BenchmarkTango_GPlus2Params 1000000 2186 ns/op 344 B/op 8 allocs/op BenchmarkTigerTonic_GPlus2Params 200000 9060 ns/op 1488 B/op 24 allocs/op BenchmarkTraffic_GPlus2Params 100000 20324 ns/op 3272 B/op 31 allocs/op BenchmarkVulcan_GPlus2Params 1000000 2039 ns/op 98 B/op 3 allocs/op BenchmarkAce_GPlusAll 300000 6603 ns/op 640 B/op 11 allocs/op BenchmarkBear_GPlusAll 100000 22363 ns/op 5488 B/op 61 allocs/op BenchmarkBeego_GPlusAll 50000 38757 ns/op 4784 B/op 52 allocs/op BenchmarkBone_GPlusAll 20000 54916 ns/op 10336 B/op 98 allocs/op BenchmarkDenco_GPlusAll 300000 4959 ns/op 672 B/op 11 allocs/op BenchmarkEcho_GPlusAll 200000 6558 ns/op 416 B/op 13 allocs/op BenchmarkGin_GPlusAll 500000 2757 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_GPlusAll 50000 34615 ns/op 8040 B/op 103 allocs/op BenchmarkGoji_GPlusAll 100000 16002 ns/op 3696 B/op 22 allocs/op BenchmarkGojiv2_GPlusAll 50000 35060 ns/op 12624 B/op 115 allocs/op BenchmarkGoJsonRest_GPlusAll 50000 41479 ns/op 8117 B/op 170 allocs/op BenchmarkGoRestful_GPlusAll 10000 131653 ns/op 32024 B/op 275 allocs/op BenchmarkGorillaMux_GPlusAll 10000 101380 ns/op 13296 B/op 142 allocs/op BenchmarkHttpRouter_GPlusAll 500000 3711 ns/op 640 B/op 11 allocs/op BenchmarkHttpTreeMux_GPlusAll 100000 14438 ns/op 4032 B/op 38 allocs/op BenchmarkKocha_GPlusAll 200000 8039 ns/op 976 B/op 43 allocs/op BenchmarkLARS_GPlusAll 500000 2630 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_GPlusAll 30000 51123 ns/op 13152 B/op 128 allocs/op BenchmarkMartini_GPlusAll 10000 176157 ns/op 14016 B/op 145 allocs/op BenchmarkPat_GPlusAll 20000 69911 ns/op 16576 B/op 298 allocs/op BenchmarkPossum_GPlusAll 100000 20716 ns/op 5408 B/op 39 allocs/op BenchmarkR2router_GPlusAll 100000 17463 ns/op 5040 B/op 63 allocs/op BenchmarkRivet_GPlusAll 300000 5142 ns/op 768 B/op 11 allocs/op BenchmarkTango_GPlusAll 50000 27321 ns/op 3656 B/op 104 allocs/op BenchmarkTigerTonic_GPlusAll 20000 77597 ns/op 14512 B/op 288 allocs/op BenchmarkTraffic_GPlusAll 10000 151406 ns/op 37360 B/op 392 allocs/op BenchmarkVulcan_GPlusAll 100000 18555 ns/op 1274 B/op 39 allocs/op ``` ## Parse.com ``` BenchmarkGin_ParseStatic 10000000 133 ns/op 0 B/op 0 allocs/op BenchmarkAce_ParseStatic 5000000 241 ns/op 0 B/op 0 allocs/op BenchmarkBear_ParseStatic 2000000 728 ns/op 120 B/op 3 allocs/op BenchmarkBeego_ParseStatic 1000000 2623 ns/op 368 B/op 4 allocs/op BenchmarkBone_ParseStatic 1000000 1285 ns/op 144 B/op 3 allocs/op BenchmarkDenco_ParseStatic 30000000 57.8 ns/op 0 B/op 0 allocs/op BenchmarkEcho_ParseStatic 5000000 342 ns/op 32 B/op 1 allocs/op BenchmarkGocraftWeb_ParseStatic 1000000 1478 ns/op 296 B/op 5 allocs/op BenchmarkGoji_ParseStatic 3000000 415 ns/op 0 B/op 0 allocs/op BenchmarkGojiv2_ParseStatic 1000000 2087 ns/op 928 B/op 7 allocs/op BenchmarkGoJsonRest_ParseStatic 1000000 1712 ns/op 329 B/op 11 allocs/op BenchmarkGoRestful_ParseStatic 200000 11072 ns/op 3224 B/op 22 allocs/op BenchmarkGorillaMux_ParseStatic 500000 4129 ns/op 752 B/op 11 allocs/op BenchmarkHttpRouter_ParseStatic 30000000 52.4 ns/op 0 B/op 0 allocs/op BenchmarkHttpTreeMux_ParseStatic 20000000 109 ns/op 0 B/op 0 allocs/op BenchmarkKocha_ParseStatic 20000000 81.8 ns/op 0 B/op 0 allocs/op BenchmarkLARS_ParseStatic 10000000 150 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseStatic 1000000 3288 ns/op 768 B/op 9 allocs/op BenchmarkMartini_ParseStatic 200000 9110 ns/op 768 B/op 9 allocs/op BenchmarkPat_ParseStatic 1000000 1135 ns/op 240 B/op 5 allocs/op BenchmarkPossum_ParseStatic 1000000 1557 ns/op 416 B/op 3 allocs/op BenchmarkR2router_ParseStatic 2000000 730 ns/op 144 B/op 4 allocs/op BenchmarkRivet_ParseStatic 10000000 121 ns/op 0 B/op 0 allocs/op BenchmarkTango_ParseStatic 1000000 1688 ns/op 248 B/op 8 allocs/op BenchmarkTigerTonic_ParseStatic 3000000 427 ns/op 48 B/op 1 allocs/op BenchmarkTraffic_ParseStatic 500000 5962 ns/op 1816 B/op 20 allocs/op BenchmarkVulcan_ParseStatic 2000000 969 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParseParam 3000000 497 ns/op 64 B/op 1 allocs/op BenchmarkBear_ParseParam 1000000 1473 ns/op 467 B/op 5 allocs/op BenchmarkBeego_ParseParam 1000000 2384 ns/op 368 B/op 4 allocs/op BenchmarkBone_ParseParam 1000000 2513 ns/op 768 B/op 6 allocs/op BenchmarkDenco_ParseParam 5000000 364 ns/op 64 B/op 1 allocs/op BenchmarkEcho_ParseParam 5000000 418 ns/op 32 B/op 1 allocs/op BenchmarkGin_ParseParam 10000000 163 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParseParam 1000000 2361 ns/op 664 B/op 8 allocs/op BenchmarkGoji_ParseParam 1000000 1590 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_ParseParam 1000000 2851 ns/op 976 B/op 9 allocs/op BenchmarkGoJsonRest_ParseParam 1000000 2965 ns/op 649 B/op 13 allocs/op BenchmarkGoRestful_ParseParam 200000 12207 ns/op 3544 B/op 23 allocs/op BenchmarkGorillaMux_ParseParam 500000 5187 ns/op 1088 B/op 12 allocs/op BenchmarkHttpRouter_ParseParam 5000000 275 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_ParseParam 1000000 1108 ns/op 352 B/op 3 allocs/op BenchmarkKocha_ParseParam 3000000 495 ns/op 56 B/op 3 allocs/op BenchmarkLARS_ParseParam 10000000 192 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseParam 500000 4103 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_ParseParam 200000 9878 ns/op 1072 B/op 10 allocs/op BenchmarkPat_ParseParam 500000 3657 ns/op 1120 B/op 17 allocs/op BenchmarkPossum_ParseParam 1000000 2084 ns/op 560 B/op 6 allocs/op BenchmarkR2router_ParseParam 1000000 1251 ns/op 432 B/op 5 allocs/op BenchmarkRivet_ParseParam 5000000 335 ns/op 48 B/op 1 allocs/op BenchmarkTango_ParseParam 1000000 1854 ns/op 280 B/op 8 allocs/op BenchmarkTigerTonic_ParseParam 500000 4582 ns/op 1008 B/op 17 allocs/op BenchmarkTraffic_ParseParam 200000 8125 ns/op 2248 B/op 23 allocs/op BenchmarkVulcan_ParseParam 1000000 1148 ns/op 98 B/op 3 allocs/op BenchmarkAce_Parse2Params 3000000 539 ns/op 64 B/op 1 allocs/op BenchmarkBear_Parse2Params 1000000 1778 ns/op 496 B/op 5 allocs/op BenchmarkBeego_Parse2Params 1000000 2519 ns/op 368 B/op 4 allocs/op BenchmarkBone_Parse2Params 1000000 2596 ns/op 720 B/op 5 allocs/op BenchmarkDenco_Parse2Params 3000000 492 ns/op 64 B/op 1 allocs/op BenchmarkEcho_Parse2Params 3000000 484 ns/op 32 B/op 1 allocs/op BenchmarkGin_Parse2Params 10000000 193 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Parse2Params 1000000 2575 ns/op 712 B/op 9 allocs/op BenchmarkGoji_Parse2Params 1000000 1373 ns/op 336 B/op 2 allocs/op BenchmarkGojiv2_Parse2Params 500000 2416 ns/op 960 B/op 8 allocs/op BenchmarkGoJsonRest_Parse2Params 300000 3452 ns/op 713 B/op 14 allocs/op BenchmarkGoRestful_Parse2Params 100000 17719 ns/op 6008 B/op 25 allocs/op BenchmarkGorillaMux_Parse2Params 300000 5102 ns/op 1088 B/op 11 allocs/op BenchmarkHttpRouter_Parse2Params 5000000 303 ns/op 64 B/op 1 allocs/op BenchmarkHttpTreeMux_Parse2Params 1000000 1372 ns/op 384 B/op 4 allocs/op BenchmarkKocha_Parse2Params 2000000 874 ns/op 128 B/op 5 allocs/op BenchmarkLARS_Parse2Params 10000000 192 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_Parse2Params 500000 3871 ns/op 1056 B/op 10 allocs/op BenchmarkMartini_Parse2Params 200000 9954 ns/op 1152 B/op 11 allocs/op BenchmarkPat_Parse2Params 500000 4194 ns/op 832 B/op 17 allocs/op BenchmarkPossum_Parse2Params 1000000 2121 ns/op 560 B/op 6 allocs/op BenchmarkR2router_Parse2Params 1000000 1415 ns/op 432 B/op 5 allocs/op BenchmarkRivet_Parse2Params 3000000 457 ns/op 96 B/op 1 allocs/op BenchmarkTango_Parse2Params 1000000 1914 ns/op 312 B/op 8 allocs/op BenchmarkTigerTonic_Parse2Params 300000 6895 ns/op 1408 B/op 24 allocs/op BenchmarkTraffic_Parse2Params 200000 8317 ns/op 2040 B/op 22 allocs/op BenchmarkVulcan_Parse2Params 1000000 1274 ns/op 98 B/op 3 allocs/op BenchmarkAce_ParseAll 200000 10401 ns/op 640 B/op 16 allocs/op BenchmarkBear_ParseAll 50000 37743 ns/op 8928 B/op 110 allocs/op BenchmarkBeego_ParseAll 20000 63193 ns/op 9568 B/op 104 allocs/op BenchmarkBone_ParseAll 20000 61767 ns/op 14160 B/op 131 allocs/op BenchmarkDenco_ParseAll 300000 7036 ns/op 928 B/op 16 allocs/op BenchmarkEcho_ParseAll 200000 11824 ns/op 832 B/op 26 allocs/op BenchmarkGin_ParseAll 300000 4199 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_ParseAll 30000 51758 ns/op 13728 B/op 181 allocs/op BenchmarkGoji_ParseAll 50000 29614 ns/op 5376 B/op 32 allocs/op BenchmarkGojiv2_ParseAll 20000 68676 ns/op 24464 B/op 199 allocs/op BenchmarkGoJsonRest_ParseAll 20000 76135 ns/op 13866 B/op 321 allocs/op BenchmarkGoRestful_ParseAll 5000 389487 ns/op 110928 B/op 600 allocs/op BenchmarkGorillaMux_ParseAll 10000 221250 ns/op 24864 B/op 292 allocs/op BenchmarkHttpRouter_ParseAll 200000 6444 ns/op 640 B/op 16 allocs/op BenchmarkHttpTreeMux_ParseAll 50000 30702 ns/op 5728 B/op 51 allocs/op BenchmarkKocha_ParseAll 200000 13712 ns/op 1112 B/op 54 allocs/op BenchmarkLARS_ParseAll 300000 6925 ns/op 0 B/op 0 allocs/op BenchmarkMacaron_ParseAll 20000 96278 ns/op 24576 B/op 250 allocs/op BenchmarkMartini_ParseAll 5000 271352 ns/op 25072 B/op 253 allocs/op BenchmarkPat_ParseAll 20000 74941 ns/op 17264 B/op 343 allocs/op BenchmarkPossum_ParseAll 50000 39947 ns/op 10816 B/op 78 allocs/op BenchmarkR2router_ParseAll 50000 42479 ns/op 8352 B/op 120 allocs/op BenchmarkRivet_ParseAll 200000 7726 ns/op 912 B/op 16 allocs/op BenchmarkTango_ParseAll 30000 50014 ns/op 7168 B/op 208 allocs/op BenchmarkTigerTonic_ParseAll 10000 106550 ns/op 19728 B/op 379 allocs/op BenchmarkTraffic_ParseAll 10000 216037 ns/op 57776 B/op 642 allocs/op BenchmarkVulcan_ParseAll 50000 34379 ns/op 2548 B/op 78 allocs/op ``` gin-1.3.0/CHANGELOG.md000066400000000000000000000247761333451471400141260ustar00rootroot00000000000000# CHANGELOG ### Gin 1.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 typeded) - [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 shorcut 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 shorcut 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 setted 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.3.0/CODE_OF_CONDUCT.md000066400000000000000000000062231333451471400150770ustar00rootroot00000000000000# 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.3.0/CONTRIBUTING.md000066400000000000000000000011571333451471400145320ustar00rootroot00000000000000## 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.3.0/LICENSE000066400000000000000000000021031333451471400132760ustar00rootroot00000000000000The 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.3.0/Makefile000066400000000000000000000026041333451471400137370ustar00rootroot00000000000000GOFMT ?= gofmt "-s" PACKAGES ?= $(shell go list ./... | grep -v /vendor/) VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") all: install install: deps govendor sync .PHONY: test test: sh coverage.sh .PHONY: fmt fmt: $(GOFMT) -w $(GOFILES) .PHONY: fmt-check fmt-check: # get all go files and run go fmt on them @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) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ go get -u github.com/kardianos/govendor; \ fi @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ go get -u github.com/campoy/embedmd; \ fi embedmd: embedmd -d *.md .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ go get -u github.com/golang/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) gin-1.3.0/README.md000066400000000000000000001316141333451471400135620ustar00rootroot00000000000000# 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) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) ## Contents - [Installation](#installation) - [Prerequisite](#prerequisite) - [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) - [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) - [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 HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) - [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 restart or stop](#graceful-restart-or-stop) - [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) - [Testing](#testing) - [Users](#users--) ## Installation To install Gin package, you need to install Go and set your Go workspace first. 1. Download and install it: ```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" ``` ### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) 1. `go get` govendor ```sh $ go get github.com/kardianos/govendor ``` 2. Create your project folder and `cd` inside ```sh $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" ``` 3. Vendor init your project and add gin ```sh $ govendor init $ govendor fetch github.com/gin-gonic/gin@v1.2 ``` 4. Copy a starting template inside your project ```sh $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go ``` 5. Run your project ```sh $ go run main.go ``` ## Prerequisite Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. ## 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 } ``` ``` # run example.go and visit 0.0.0.0: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 use `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 ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ```go func main() { // Disable Console Color // gin.DisableConsoleColor() // 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) }) 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](examples/upload-file/single). ```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](examples/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") } ``` ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#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`, `BindQuery` - **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`, `ShouldBindQuery` - **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" binding:"required"` Password string `form:"password" json:"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 { if json.User == "manu" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) // 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 { if form.User == "manu" && form.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) // 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](examples/custom-validation/server.go). [embedmd]:# (examples/custom-validation/server.go go) ```go package main import ( "net/http" "reflect" "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gopkg.in/go-playground/validator.v8" ) type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` } func bookableDate( v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, ) bool { if date, ok := field.Interface().(time.Time); ok { today := time.Now() if today.Year() > date.Year() || today.YearDay() > date.YearDay() { 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-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. See the [struct-lvl-validation example](examples/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" import "github.com/gin-gonic/gin" import "time" type Person struct { Name string `form:"name"` Address string `form:"address"` Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` } 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) } c.String(200, "Success") } ``` Test it with: ```sh $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" ``` ### 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 package main import ( "github.com/gin-gonic/gin" ) type LoginForm struct { User string `form:"user" binding:"required"` Password string `form:"password" binding:"required"` } func main() { router := gin.Default() router.POST("/login", 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 LoginForm // in this case proper binding will be automatically selected if c.ShouldBind(&form) == nil { if form.User == "user" && form.Password == "password" { c.JSON(200, gin.H{"status": "you are logged in"}) } else { c.JSON(401, gin.H{"status": "unauthorized"}) } } }) router.Run(":8080") } ``` Test it with: ```sh $ curl -v --form user=user --form password=password http://localhost:8080/login ``` ### XML, JSON and YAML 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}) }) // 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?callback=x", func(c *gin.Context) { data := map[string]interface{}{ "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") } ``` #### AsciiJSON Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. ```go func main() { r := gin.Default() r.GET("/someJSON", func(c *gin.Context) { data := map[string]interface{}{ "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") } ``` ### 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 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](examples/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", map[string]interface{}{ "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. [embedmd]:# (examples/auto-tls/example1/main.go go) ```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. [embedmd]:# (examples/auto-tls/example2/main.go go) ```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: [embedmd]:# (examples/multiple-service/main.go go) ```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 { return server01.ListenAndServe() }) g.Go(func() error { return server02.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } } ``` ### Graceful restart or stop Do you want to graceful restart or stop your web server? There are some ways this can be done. We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. ```go router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router) ``` An alternative to endless: * [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. If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin. [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go // +build go1.8 package main import ( "context" "log" "net/http" "os" "os/signal" "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, } go func() { // service connections 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) signal.Notify(quit, os.Interrupt) <-quit log.Println("Shutdown Server ...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server 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 { 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 `examples/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"}} ``` **NOTE**: NOT support the follow style struct: ```go type StructX struct { X struct {} `form:"name_x"` // HERE have form } type StructY struct { Y StructX `form:"name_y"` // HERE hava form } type StructZ struct { Z *StructZ `form:"name_z"` // HERE hava form } ``` In a word, only support nested custom struct which have no `form` now. ### 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. [embedmd]:# (examples/http-pusher/main.go go) ```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") } ``` ## 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. * [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. gin-1.3.0/auth.go000066400000000000000000000057051333451471400135740ustar00rootroot00000000000000// 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/subtle" "encoding/base64" "net/http" "strconv" ) // 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([]byte(base)) } func secureCompare(given, actual string) bool { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 } // Securely compare actual to itself to keep constant time, but always return false. return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false } gin-1.3.0/auth_test.go000066400000000000000000000077721333451471400146410ustar00rootroot00000000000000// 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 TestBasicAuthSecureCompare(t *testing.T) { assert.True(t, secureCompare("1234567890", "1234567890")) assert.False(t, secureCompare("123456789", "1234567890")) assert.False(t, secureCompare("12345678900", "1234567890")) assert.False(t, secureCompare("1234567891", "1234567890")) } 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.HeaderMap.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.HeaderMap.Get("WWW-Authenticate")) } gin-1.3.0/benchmarks_test.go000066400000000000000000000074321333451471400160060ustar00rootroot00000000000000// 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.3.0/binding/000077500000000000000000000000001333451471400137075ustar00rootroot00000000000000gin-1.3.0/binding/binding.go000066400000000000000000000064001333451471400156500ustar00rootroot00000000000000// 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" // 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" ) // 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 } // 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 reqest. 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{} ) // 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 MIMEMSGPACK, MIMEMSGPACK2: return MsgPack default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: return Form } } func validate(obj interface{}) error { if Validator == nil { return nil } return Validator.ValidateStruct(obj) } gin-1.3.0/binding/binding_body_test.go000066400000000000000000000026661333451471400177360ustar00rootroot00000000000000package binding import ( "bytes" "io/ioutil" "testing" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) func TestBindingBody(t *testing.T) { for _, tt := range []struct { name string binding BindingBody body string want string }{ { name: "JSON bidning", binding: JSON, body: `{"foo":"FOO"}`, }, { name: "XML bidning", binding: XML, body: ` FOO `, }, { name: "MsgPack binding", binding: MsgPack, body: msgPackBody(t), }, } { t.Logf("testing: %s", tt.name) req := requestWithBody("POST", "/", tt.body) form := FooStruct{} body, _ := ioutil.ReadAll(req.Body) assert.NoError(t, tt.binding.BindBody(body, &form)) assert.Equal(t, FooStruct{"FOO"}, form) } } func msgPackBody(t *testing.T) string { test := FooStruct{"FOO"} h := new(codec.MsgpackHandle) buf := bytes.NewBuffer(nil) assert.NoError(t, codec.NewEncoder(buf, h).Encode(test)) return buf.String() } func TestBindingBodyProto(t *testing.T) { test := protoexample.Test{ Label: proto.String("FOO"), } data, _ := proto.Marshal(&test) req := requestWithBody("POST", "/", string(data)) form := protoexample.Test{} body, _ := ioutil.ReadAll(req.Body) assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.Equal(t, test, form) } gin-1.3.0/binding/binding_test.go000066400000000000000000000755301333451471400167210ustar00rootroot00000000000000// 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/ioutil" "mime/multipart" "net/http" "testing" "time" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) 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 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 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"` } 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 { // Unknown type: not support map MapFoo map[string]interface{} `form:"map_foo"` } 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 FooBarStructForIntType struct { IntFoo int `form:"int_foo"` IntBar int `form:"int_bar" binding:"required"` } type FooBarStructForInt8Type struct { Int8Foo int8 `form:"int8_foo"` Int8Bar int8 `form:"int8_bar" binding:"required"` } type FooBarStructForInt16Type struct { Int16Foo int16 `form:"int16_foo"` Int16Bar int16 `form:"int16_bar" binding:"required"` } type FooBarStructForInt32Type struct { Int32Foo int32 `form:"int32_foo"` Int32Bar int32 `form:"int32_bar" binding:"required"` } type FooBarStructForInt64Type struct { Int64Foo int64 `form:"int64_foo"` Int64Bar int64 `form:"int64_bar" binding:"required"` } type FooBarStructForUintType struct { UintFoo uint `form:"uint_foo"` UintBar uint `form:"uint_bar" binding:"required"` } type FooBarStructForUint8Type struct { Uint8Foo uint8 `form:"uint8_foo"` Uint8Bar uint8 `form:"uint8_bar" binding:"required"` } type FooBarStructForUint16Type struct { Uint16Foo uint16 `form:"uint16_foo"` Uint16Bar uint16 `form:"uint16_bar" binding:"required"` } type FooBarStructForUint32Type struct { Uint32Foo uint32 `form:"uint32_foo"` Uint32Bar uint32 `form:"uint32_bar" binding:"required"` } type FooBarStructForUint64Type struct { Uint64Foo uint64 `form:"uint64_foo"` Uint64Bar uint64 `form:"uint64_bar" binding:"required"` } type FooBarStructForBoolType struct { BoolFoo bool `form:"bool_foo"` BoolBar bool `form:"bool_bar" binding:"required"` } type FooBarStructForFloat32Type struct { Float32Foo float32 `form:"float32_foo"` Float32Bar float32 `form:"float32_bar" binding:"required"` } type FooBarStructForFloat64Type struct { Float64Foo float64 `form:"float64_foo"` Float64Bar float64 `form:"float64_bar" binding:"required"` } 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, Form, Default("POST", MIMEMultipartPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) } 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 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 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=", "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=", "/?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 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=", "bar2=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", "/", "/", "int_foo=&int_bar=-12", "bar2=-123", "Int") testFormBindingForType(t, "GET", "/?int_foo=&int_bar=-12", "/?bar2=-123", "", "", "Int") testFormBindingForType(t, "POST", "/", "/", "int8_foo=&int8_bar=-12", "bar2=-123", "Int8") testFormBindingForType(t, "GET", "/?int8_foo=&int8_bar=-12", "/?bar2=-123", "", "", "Int8") testFormBindingForType(t, "POST", "/", "/", "int16_foo=&int16_bar=-12", "bar2=-123", "Int16") testFormBindingForType(t, "GET", "/?int16_foo=&int16_bar=-12", "/?bar2=-123", "", "", "Int16") testFormBindingForType(t, "POST", "/", "/", "int32_foo=&int32_bar=-12", "bar2=-123", "Int32") testFormBindingForType(t, "GET", "/?int32_foo=&int32_bar=-12", "/?bar2=-123", "", "", "Int32") testFormBindingForType(t, "POST", "/", "/", "int64_foo=&int64_bar=-12", "bar2=-123", "Int64") testFormBindingForType(t, "GET", "/?int64_foo=&int64_bar=-12", "/?bar2=-123", "", "", "Int64") testFormBindingForType(t, "POST", "/", "/", "uint_foo=&uint_bar=12", "bar2=123", "Uint") testFormBindingForType(t, "GET", "/?uint_foo=&uint_bar=12", "/?bar2=123", "", "", "Uint") testFormBindingForType(t, "POST", "/", "/", "uint8_foo=&uint8_bar=12", "bar2=123", "Uint8") testFormBindingForType(t, "GET", "/?uint8_foo=&uint8_bar=12", "/?bar2=123", "", "", "Uint8") testFormBindingForType(t, "POST", "/", "/", "uint16_foo=&uint16_bar=12", "bar2=123", "Uint16") testFormBindingForType(t, "GET", "/?uint16_foo=&uint16_bar=12", "/?bar2=123", "", "", "Uint16") testFormBindingForType(t, "POST", "/", "/", "uint32_foo=&uint32_bar=12", "bar2=123", "Uint32") testFormBindingForType(t, "GET", "/?uint32_foo=&uint32_bar=12", "/?bar2=123", "", "", "Uint32") testFormBindingForType(t, "POST", "/", "/", "uint64_foo=&uint64_bar=12", "bar2=123", "Uint64") testFormBindingForType(t, "GET", "/?uint64_foo=&uint64_bar=12", "/?bar2=123", "", "", "Uint64") testFormBindingForType(t, "POST", "/", "/", "bool_foo=&bool_bar=true", "bar2=true", "Bool") testFormBindingForType(t, "GET", "/?bool_foo=&bool_bar=true", "/?bar2=true", "", "", "Bool") testFormBindingForType(t, "POST", "/", "/", "float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32") testFormBindingForType(t, "GET", "/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3", "", "", "Float32") testFormBindingForType(t, "POST", "/", "/", "float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64") testFormBindingForType(t, "GET", "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", "", "", "Float64") 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 createFormPostRequest() *http.Request { req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest() *http.Request { req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestFail() *http.Request { req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormMultipartRequest() *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() mw.SetBoundary(boundary) mw.WriteField("foo", "bar") mw.WriteField("bar", "foo") req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func createFormMultipartRequestFail() *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() mw.SetBoundary(boundary) mw.WriteField("map_foo", "bar") req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct 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() var obj FooDefaultBarStruct FormPost.Bind(req, &obj) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) } func TestBindingFormPostFail(t *testing.T) { req := createFormPostRequestFail() var obj FooStructForMapType err := FormPost.Bind(req, &obj) assert.Error(t, err) } func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest() var obj FooBarStruct 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 TestBindingFormMultipartFail(t *testing.T) { req := createFormMultipartRequestFail() 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 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 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 TestExistsSucceeds(t *testing.T) { type HogeStruct struct { Hoge *int `json:"hoge" binding:"exists"` } var obj HogeStruct req := requestWithBody("POST", "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) assert.NoError(t, err) } func TestExistsFails(t *testing.T) { type HogeStruct struct { Hoge *int `json:"foo" binding:"exists"` } var obj HogeStruct req := requestWithBody("POST", "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) assert.Error(t, err) } 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 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()) obj = FooBarStructForTimeType{} 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 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 "Int": obj := FooBarStructForIntType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int(0), obj.IntFoo) assert.Equal(t, int(-12), obj.IntBar) obj = FooBarStructForIntType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Int8": obj := FooBarStructForInt8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int8(0), obj.Int8Foo) assert.Equal(t, int8(-12), obj.Int8Bar) obj = FooBarStructForInt8Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Int16": obj := FooBarStructForInt16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int16(0), obj.Int16Foo) assert.Equal(t, int16(-12), obj.Int16Bar) obj = FooBarStructForInt16Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Int32": obj := FooBarStructForInt32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int32(0), obj.Int32Foo) assert.Equal(t, int32(-12), obj.Int32Bar) obj = FooBarStructForInt32Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Int64": obj := FooBarStructForInt64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, int64(0), obj.Int64Foo) assert.Equal(t, int64(-12), obj.Int64Bar) obj = FooBarStructForInt64Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Uint": obj := FooBarStructForUintType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, uint(0x0), obj.UintFoo) assert.Equal(t, uint(0xc), obj.UintBar) obj = FooBarStructForUintType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Uint8": obj := FooBarStructForUint8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, uint8(0x0), obj.Uint8Foo) assert.Equal(t, uint8(0xc), obj.Uint8Bar) obj = FooBarStructForUint8Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Uint16": obj := FooBarStructForUint16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, uint16(0x0), obj.Uint16Foo) assert.Equal(t, uint16(0xc), obj.Uint16Bar) obj = FooBarStructForUint16Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Uint32": obj := FooBarStructForUint32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, uint32(0x0), obj.Uint32Foo) assert.Equal(t, uint32(0xc), obj.Uint32Bar) obj = FooBarStructForUint32Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Uint64": obj := FooBarStructForUint64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, uint64(0x0), obj.Uint64Foo) assert.Equal(t, uint64(0xc), obj.Uint64Bar) obj = FooBarStructForUint64Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Float32": obj := FooBarStructForFloat32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, float32(0.0), obj.Float32Foo) assert.Equal(t, float32(-12.34), obj.Float32Bar) obj = FooBarStructForFloat32Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Float64": obj := FooBarStructForFloat64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, float64(0.0), obj.Float64Foo) assert.Equal(t, float64(-12.34), obj.Float64Bar) obj = FooBarStructForFloat64Type{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) case "Bool": obj := FooBarStructForBoolType{} err := b.Bind(req, &obj) assert.NoError(t, err) assert.False(t, obj.BoolFoo) assert.True(t, obj.BoolBar) obj = FooBarStructForBoolType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) 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.Error(t, err) 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 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 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 requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } gin-1.3.0/binding/default_validator.go000066400000000000000000000024621333451471400177330ustar00rootroot00000000000000// 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" "gopkg.in/go-playground/validator.v8" ) 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() { config := &validator.Config{TagName: "binding"} v.validate = validator.New(config) }) } gin-1.3.0/binding/form.go000066400000000000000000000023641333451471400152060ustar00rootroot00000000000000// 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 * 1024 * 1024 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 } req.ParseMultipartForm(defaultMemory) 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 := mapForm(obj, req.MultipartForm.Value); err != nil { return err } return validate(obj) } gin-1.3.0/binding/form_mapping.go000066400000000000000000000122211333451471400167120ustar00rootroot00000000000000// 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" "reflect" "strconv" "strings" "time" ) func mapForm(ptr interface{}, form map[string][]string) error { typ := reflect.TypeOf(ptr).Elem() val := reflect.ValueOf(ptr).Elem() for i := 0; i < typ.NumField(); i++ { typeField := typ.Field(i) structField := val.Field(i) if !structField.CanSet() { continue } structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get("form") inputFieldNameList := strings.Split(inputFieldName, ",") inputFieldName = inputFieldNameList[0] var defaultValue string if len(inputFieldNameList) > 1 { defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) if defaultList[0] == "default" { defaultValue = defaultList[1] } } if inputFieldName == "" { inputFieldName = typeField.Name // if "form" tag is nil, we inspect if the field is a struct or struct pointer. // this would not make sense for JSON parsing but it does for a form // since data is flatten if structFieldKind == reflect.Ptr { if !structField.Elem().IsValid() { structField.Set(reflect.New(structField.Type().Elem())) } structField = structField.Elem() structFieldKind = structField.Kind() } if structFieldKind == reflect.Struct { err := mapForm(structField.Addr().Interface(), form) if err != nil { return err } continue } } inputValue, exists := form[inputFieldName] if !exists { if defaultValue == "" { continue } inputValue = make([]string, 1) inputValue[0] = defaultValue } numElems := len(inputValue) if structFieldKind == reflect.Slice && numElems > 0 { sliceOf := structField.Type().Elem().Kind() slice := reflect.MakeSlice(structField.Type(), numElems, numElems) for i := 0; i < numElems; i++ { if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil { return err } } val.Field(i).Set(slice) } else { if _, isTime := structField.Interface().(time.Time); isTime { if err := setTimeField(inputValue[0], typeField, structField); err != nil { return err } continue } if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { return err } } } return nil } func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { switch valueKind { case reflect.Int: return setIntField(val, 0, structField) case reflect.Int8: return setIntField(val, 8, structField) case reflect.Int16: return setIntField(val, 16, structField) case reflect.Int32: return setIntField(val, 32, structField) case reflect.Int64: return setIntField(val, 64, structField) case reflect.Uint: return setUintField(val, 0, structField) case reflect.Uint8: return setUintField(val, 8, structField) case reflect.Uint16: return setUintField(val, 16, structField) case reflect.Uint32: return setUintField(val, 32, structField) case reflect.Uint64: return setUintField(val, 64, structField) case reflect.Bool: return setBoolField(val, structField) case reflect.Float32: return setFloatField(val, 32, structField) case reflect.Float64: return setFloatField(val, 64, structField) case reflect.String: structField.SetString(val) case reflect.Ptr: if !structField.Elem().IsValid() { structField.Set(reflect.New(structField.Type().Elem())) } structFieldElem := structField.Elem() return setWithProperType(structFieldElem.Kind(), val, structFieldElem) default: return errors.New("Unknown type") } 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 == "" { return errors.New("Blank time format") } 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 } gin-1.3.0/binding/json.go000066400000000000000000000020021333451471400152010ustar00rootroot00000000000000// 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" "io" "net/http" "github.com/gin-gonic/gin/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 type jsonBinding struct{} func (jsonBinding) Name() string { return "json" } func (jsonBinding) Bind(req *http.Request, obj interface{}) error { 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 err := decoder.Decode(obj); err != nil { return err } return validate(obj) } gin-1.3.0/binding/msgpack.go000066400000000000000000000014021333451471400156600ustar00rootroot00000000000000// 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 ( "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.3.0/binding/protobuf.go000066400000000000000000000015411333451471400160770ustar00rootroot00000000000000// 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 cann't add // `binding:""` to the struct which automatically generate by gen-proto return nil // return validate(obj) } gin-1.3.0/binding/query.go000066400000000000000000000007251333451471400154070ustar00rootroot00000000000000// 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.3.0/binding/validate_test.go000066400000000000000000000133151333451471400170710ustar00rootroot00000000000000// 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" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "gopkg.in/go-playground/validator.v8" ) 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"` } // notOne is a custom validator meant to be used with `validator.v8` library. // The method signature for `v9` is significantly different and this function // would need to be changed for tests to pass after upgrade. // See https://github.com/gin-gonic/gin/pull/1015. func notOne( v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, ) bool { if val, ok := 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.3.0/binding/xml.go000066400000000000000000000012751333451471400150430ustar00rootroot00000000000000// 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.3.0/codecov.yml000066400000000000000000000001561333451471400144440ustar00rootroot00000000000000coverage: notify: gitter: default: url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165 gin-1.3.0/context.go000066400000000000000000000711531333451471400143170ustar00rootroot00000000000000// 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" "io" "io/ioutil" "math" "mime/multipart" "net" "net/http" "net/url" "os" "strings" "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 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 engine *Engine // 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 } /************************************/ /********** CONTEXT CREATION ********/ /************************************/ func (c *Context) reset() { c.Writer = &c.writermem c.Params = c.Params[0:0] c.handlers = nil c.index = -1 c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = 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 { var cp = *c cp.writermem.ResponseWriter = nil cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil 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()) } // Handler returns the main handler. func (c *Context) Handler() HandlerFunc { return c.handlers.Last() } /************************************/ /*********** 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 s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } } // 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{}) { if c.Keys == nil { c.Keys = make(map[string]interface{}) } c.Keys[key] = value } // 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) { value, exists = c.Keys[key] 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 } // 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) { if values, ok := c.Request.URL.Query()[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) { return c.get(c.Request.URL.Query(), 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 } // 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) { req := c.Request req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true } if req.MultipartForm != nil && req.MultipartForm.File != nil { if values := req.MultipartForm.Value[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) { req := c.Request req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) dicts, exist := c.get(req.PostForm, key) if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { dicts, exist = c.get(req.MultipartForm.Value, key) } return dicts, exist } // 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) { _, fh, err := c.Request.FormFile(name) 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() io.Copy(out, src) return nil } // 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) } // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) } // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) } return } // 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) } // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). func (c *Context) ShouldBindQuery(obj interface{}) error { return c.ShouldBindWith(obj, binding.Query) } // 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.ToLower(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.writermem.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) } else { 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) } // 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, 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 } 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}) } else { 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}) } // 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}) } // 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) } // 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, }) } func (c *Context) Stream(step func(w io.Writer) bool) { w := c.Writer clientGone := w.CloseNotify() for { select { case <-clientGone: return default: keepOpen := step(w) w.Flush() if !keepOpen { return } } } } /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ type Negotiate struct { Offered []string HTMLName string HTMLData interface{} JSONData interface{} XMLData interface{} Data interface{} } 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) default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) } } 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 _, offert := range offered { if accepted == offert { return offert } } } return "" } func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } /************************************/ /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. func (c *Context) Done() <-chan struct{} { return nil } // Err returns a non-nil error value after Done is closed, // successive calls to Err return the same error. // If Done is not yet closed, Err returns nil. // If Done is closed, Err returns a non-nil error explaining why: // Canceled if the context was canceled // or DeadlineExceeded if the context's deadline passed. 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.3.0/context_appengine.go000066400000000000000000000003631333451471400163400ustar00rootroot00000000000000// +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.3.0/context_test.go000066400000000000000000001407621333451471400153610ustar00rootroot00000000000000// 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" "html/template" "io" "mime/multipart" "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) 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) { w.Write([]byte("test")) } 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 TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) mw.WriteField("foo", "bar") w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { w.Write([]byte("test")) } 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) { w.Write([]byte("test")) } 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")) 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) } 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 handlerNameTest(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.SetCookie("user", "gin", 1, "/", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie")) } func TestContextSetCookiePathEmpty(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.SetCookie("user", "gin", 1, "", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", 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) { // todo(thinkerou): go1.6 not support StatusProcessing assert.False(t, false, bodyAllowedForStatus(102)) 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 func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) 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/json; charset=utf-8", w.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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) var b bytes.Buffer setup(&b) defer teardown() templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) 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", b.String()) 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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.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.HeaderMap.Get("Content-Type")) } // 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.HeaderMap.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") }) // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // when we upgrade go version we can use http.StatusPermanentRedirect assert.NotPanics(t, func() { c.Redirect(308, "/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}, 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.HeaderMap.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}, 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.HeaderMap.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.HeaderMap.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 TestContextNegotiationFormatCustum(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) buf.ReadFrom(w.Body) 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) c.Error(errors.New("first error")) assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) c.Error(&Error{ Err: errors.New("second error"), Meta: "some data 2", Type: ErrorTypePublic, }) assert.Len(t, c.Errors, 2) assert.Equal(t, errors.New("first error"), c.Errors[0].Err) assert.Nil(t, c.Errors[0].Meta) assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type) assert.Equal(t, errors.New("second error"), 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) } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) 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") 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 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 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 TestContextShouldBindWithQuery(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.ShouldBindQuery(&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.HeaderMap.Get("Content-Type")) assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } 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 }() w.Write([]byte("test")) 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() }() writer.Write([]byte("test")) return true }) assert.Equal(t, "test", w.Body.String()) } gin-1.3.0/coverage.sh000066400000000000000000000005171333451471400144270ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "mode: count" > coverage.out for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do go test -v -covermode=count -coverprofile=profile.out $d if [ -f profile.out ]; then cat profile.out | grep -v "mode:" >> coverage.out rm profile.out fi done gin-1.3.0/debug.go000066400000000000000000000037621333451471400137220ustar00rootroot00000000000000// 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" "html/template" "log" ) func init() { log.SetFlags(0) } // 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 } func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) handlerName := nameOfFunction(handlers.Last()) debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) } } func debugPrintLoadTemplate(tmpl *template.Template) { if IsDebugging() { var buf bytes.Buffer 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() { log.Printf("[GIN-debug] "+format, values...) } } func debugPrintWARNINGDefault() { debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 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 { debugPrint("[ERROR] %v\n", err) } } gin-1.3.0/debug_test.go000066400000000000000000000063151333451471400147560ustar00rootroot00000000000000// 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" "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) { var w bytes.Buffer setup(&w) defer teardown() SetMode(ReleaseMode) debugPrint("DEBUG this!") SetMode(TestMode) debugPrint("DEBUG this!") assert.Empty(t, w.String()) SetMode(DebugMode) debugPrint("these are %d %s\n", 2, "error messages") assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String()) } func TestDebugPrintError(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() SetMode(DebugMode) debugPrintError(nil) assert.Empty(t, w.String()) debugPrintError(errors.New("this is an error")) assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String()) } func TestDebugPrintRoutes(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String()) } func TestDebugPrintLoadTemplate(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() debugPrintWARNINGSetHTMLTemplate() 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", w.String()) } func TestDebugPrintWARNINGDefault(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() debugPrintWARNINGDefault() assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) } func TestDebugPrintWARNINGNew(t *testing.T) { var w bytes.Buffer setup(&w) defer teardown() debugPrintWARNINGNew() 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", w.String()) } func setup(w io.Writer) { SetMode(DebugMode) log.SetOutput(w) } func teardown() { SetMode(TestMode) log.SetOutput(os.Stdout) } gin-1.3.0/deprecated.go000066400000000000000000000013251333451471400147250ustar00rootroot00000000000000// 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.3.0/deprecated_test.go000066400000000000000000000013441333451471400157650ustar00rootroot00000000000000// 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"` } 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.3.0/doc.go000066400000000000000000000002651333451471400133740ustar00rootroot00000000000000/* Package gin implements a HTTP web framework called gin. See https://gin-gonic.github.io/gin/ for more information about gin. */ package gin // import "github.com/gin-gonic/gin" gin-1.3.0/errors.go000066400000000000000000000063061333451471400141450ustar00rootroot00000000000000// 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" "reflect" "github.com/gin-gonic/gin/json" ) type ErrorType uint64 const ( ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails ErrorTypePrivate ErrorType = 1 << 0 ErrorTypePublic ErrorType = 1 << 1 ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeNu = 2 ) type Error struct { Err error Type ErrorType Meta interface{} } type errorMsgs []*Error var _ error = &Error{} func (msg *Error) SetType(flags ErrorType) *Error { msg.Type = flags return msg } func (msg *Error) SetMeta(data interface{}) *Error { msg.Meta = data return msg } 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() } 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 } } func (a errorMsgs) MarshalJSON() ([]byte, error) { return json.Marshal(a.JSON()) } func (a errorMsgs) String() string { if len(a) == 0 { return "" } var buffer bytes.Buffer 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.3.0/errors_test.go000066400000000000000000000060721333451471400152040ustar00rootroot00000000000000// 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/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{ "status": "200", "data": "some data", }) assert.Equal(t, H{ "error": baseError.Error(), "status": "200", "data": "some data", }, err.JSON()) err.SetMeta(H{ "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"}) 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.3.0/examples/000077500000000000000000000000001333451471400141135ustar00rootroot00000000000000gin-1.3.0/examples/app-engine/000077500000000000000000000000001333451471400161365ustar00rootroot00000000000000gin-1.3.0/examples/app-engine/README.md000066400000000000000000000011061333451471400174130ustar00rootroot00000000000000# Guide to run Gin under App Engine LOCAL Development Server 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) 2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` 3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` 4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` 5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) gin-1.3.0/examples/app-engine/app.yaml000066400000000000000000000001421333451471400175770ustar00rootroot00000000000000application: hello version: 1 runtime: go api_version: go1 handlers: - url: /.* script: _go_appgin-1.3.0/examples/app-engine/hello.go000066400000000000000000000007411333451471400175720ustar00rootroot00000000000000package hello import ( "net/http" "github.com/gin-gonic/gin" ) // This function's name is a must. App Engine uses it to drive the requests properly. func init() { // Starts a new Gin instance with no middle-ware r := gin.New() // Define your handlers r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World!") }) r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) // Handle all requests using net/http http.Handle("/", r) } gin-1.3.0/examples/assets-in-binary/000077500000000000000000000000001333451471400173035ustar00rootroot00000000000000gin-1.3.0/examples/assets-in-binary/README.md000066400000000000000000000007561333451471400205720ustar00rootroot00000000000000# Building a single binary containing templates This is a complete example to create a single binary with the [gin-gonic/gin][gin] Web Server with HTML templates. [gin]: https://github.com/gin-gonic/gin ## How to use ### Prepare Packages ``` go get github.com/gin-gonic/gin go get github.com/jessevdk/go-assets-builder ``` ### Generate assets.go ``` go-assets-builder html -o assets.go ``` ### Build the server ``` go build -o assets-in-binary ``` ### Run ``` ./assets-in-binary ``` gin-1.3.0/examples/assets-in-binary/assets.go000066400000000000000000000022011333451471400211270ustar00rootroot00000000000000package main import ( "time" "github.com/jessevdk/go-assets" ) var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

Can you see this? → {{.Bar}}

\n\n" var _Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2 = "\n\n

Hello, {{.Foo}}

\n\n" // Assets returns go-assets FileSystem var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{ "/": { Path: "/", FileMode: 0x800001ed, Mtime: time.Unix(1524365738, 1524365738517125470), Data: nil, }, "/html": { Path: "/html", FileMode: 0x800001ed, Mtime: time.Unix(1524365491, 1524365491289799093), Data: nil, }, "/html/bar.tmpl": { Path: "/html/bar.tmpl", FileMode: 0x1a4, Mtime: time.Unix(1524365491, 1524365491289611557), Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003), }, "/html/index.tmpl": { Path: "/html/index.tmpl", FileMode: 0x1a4, Mtime: time.Unix(1524365491, 1524365491289995821), Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2), }}, "") gin-1.3.0/examples/assets-in-binary/html/000077500000000000000000000000001333451471400202475ustar00rootroot00000000000000gin-1.3.0/examples/assets-in-binary/html/bar.tmpl000066400000000000000000000001071333451471400217070ustar00rootroot00000000000000

Can you see this? → {{.Bar}}

gin-1.3.0/examples/assets-in-binary/html/index.tmpl000066400000000000000000000000701333451471400222510ustar00rootroot00000000000000

Hello, {{.Foo}}

gin-1.3.0/examples/assets-in-binary/main.go000066400000000000000000000014741333451471400205640ustar00rootroot00000000000000package main import ( "html/template" "io/ioutil" "net/http" "strings" "github.com/gin-gonic/gin" ) 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", gin.H{ "Foo": "World", }) }) r.GET("/bar", func(c *gin.Context) { c.HTML(http.StatusOK, "/html/bar.tmpl", gin.H{ "Bar": "World", }) }) r.Run(":8080") } func loadTemplate() (*template.Template, error) { t := template.New("") for name, file := range Assets.Files { 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 } gin-1.3.0/examples/auto-tls/000077500000000000000000000000001333451471400156635ustar00rootroot00000000000000gin-1.3.0/examples/auto-tls/example1/000077500000000000000000000000001333451471400173775ustar00rootroot00000000000000gin-1.3.0/examples/auto-tls/example1/main.go000066400000000000000000000004231333451471400206510ustar00rootroot00000000000000package 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")) } gin-1.3.0/examples/auto-tls/example2/000077500000000000000000000000001333451471400174005ustar00rootroot00000000000000gin-1.3.0/examples/auto-tls/example2/main.go000066400000000000000000000007371333451471400206620ustar00rootroot00000000000000package 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)) } gin-1.3.0/examples/basic/000077500000000000000000000000001333451471400151745ustar00rootroot00000000000000gin-1.3.0/examples/basic/main.go000066400000000000000000000024641333451471400164550ustar00rootroot00000000000000package main import ( "net/http" "github.com/gin-gonic/gin" ) var DB = make(map[string]string) func setupRouter() *gin.Engine { // Disable Console Color // gin.DisableConsoleColor() r := gin.Default() // Ping test r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) // Get user value r.GET("/user/:name", func(c *gin.Context) { user := c.Params.ByName("name") value, ok := DB[user] if ok { c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) } }) // Authorized group (uses gin.BasicAuth() middleware) // Same than: // authorized := r.Group("/") // authorized.Use(gin.BasicAuth(gin.Credentials{ // "foo": "bar", // "manu": "123", //})) authorized := r.Group("/", gin.BasicAuth(gin.Accounts{ "foo": "bar", // user:foo password:bar "manu": "123", // user:manu password:123 })) authorized.POST("admin", func(c *gin.Context) { user := c.MustGet(gin.AuthUserKey).(string) // Parse JSON var json struct { Value string `json:"value" binding:"required"` } if c.Bind(&json) == nil { DB[user] = json.Value c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) return r } func main() { r := setupRouter() // Listen and Server in 0.0.0.0:8080 r.Run(":8080") } gin-1.3.0/examples/basic/main_test.go000066400000000000000000000005461333451471400175130ustar00rootroot00000000000000package 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, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } gin-1.3.0/examples/custom-validation/000077500000000000000000000000001333451471400175555ustar00rootroot00000000000000gin-1.3.0/examples/custom-validation/server.go000066400000000000000000000023311333451471400214110ustar00rootroot00000000000000package main import ( "net/http" "reflect" "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gopkg.in/go-playground/validator.v8" ) type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` } func bookableDate( v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, ) bool { if date, ok := field.Interface().(time.Time); ok { today := time.Now() if today.Year() > date.Year() || today.YearDay() > date.YearDay() { 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()}) } } gin-1.3.0/examples/favicon/000077500000000000000000000000001333451471400155405ustar00rootroot00000000000000gin-1.3.0/examples/favicon/favicon.ico000066400000000000000000000021761333451471400176670ustar00rootroot00000000000000 h(    ==DD00++/++,22YYII)) &v!!33ywA Ue& V 'Di  )%bvm7юֶ@ވ  2   !!!! @!!!!!!00OO==w<<<<++,11<<==< 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") } gin-1.3.0/examples/http-pusher/testdata/000077500000000000000000000000001333451471400202075ustar00rootroot00000000000000gin-1.3.0/examples/http-pusher/testdata/ca.pem000066400000000000000000000015271333451471400213020ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 +L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG Dfcog5wrJytaQ6UA0wE= -----END CERTIFICATE----- gin-1.3.0/examples/http-pusher/testdata/server.key000066400000000000000000000016201333451471400222260ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf 3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR 81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe F98XJ7tIFfJq -----END PRIVATE KEY----- gin-1.3.0/examples/http-pusher/testdata/server.pem000066400000000000000000000017041333451471400222220ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd 9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 -----END CERTIFICATE----- gin-1.3.0/examples/http2/000077500000000000000000000000001333451471400151545ustar00rootroot00000000000000gin-1.3.0/examples/http2/README.md000066400000000000000000000006071333451471400164360ustar00rootroot00000000000000## How to generate RSA private key and digital certificate 1. Install Openssl Please visit https://github.com/openssl/openssl to get pkg and install. 2. Generate RSA private key ```sh $ mkdir testdata $ openssl genrsa -out ./testdata/server.key 2048 ``` 3. Generate digital certificate ```sh $ openssl req -new -x509 -key ./testdata/server.key -out ./testdata/server.pem -days 365 ``` gin-1.3.0/examples/http2/main.go000066400000000000000000000013311333451471400164250ustar00rootroot00000000000000package main import ( "html/template" "log" "net/http" "os" "github.com/gin-gonic/gin" ) var html = template.Must(template.New("https").Parse(` Https Test

Welcome, Ginner!

`)) func main() { logger := log.New(os.Stderr, "", 0) logger.Println("[WARNING] DON'T USE THE EMBED CERTS FROM THIS EXAMPLE IN PRODUCTION ENVIRONMENT, GENERATE YOUR OWN!") r := gin.Default() r.SetHTMLTemplate(html) r.GET("/welcome", func(c *gin.Context) { c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) }) // Listen and Server in https://127.0.0.1:8080 r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") } gin-1.3.0/examples/http2/testdata/000077500000000000000000000000001333451471400167655ustar00rootroot00000000000000gin-1.3.0/examples/http2/testdata/ca.pem000066400000000000000000000015271333451471400200600ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 +L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG Dfcog5wrJytaQ6UA0wE= -----END CERTIFICATE----- gin-1.3.0/examples/http2/testdata/server.key000066400000000000000000000016201333451471400210040ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf 3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR 81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe F98XJ7tIFfJq -----END PRIVATE KEY----- gin-1.3.0/examples/http2/testdata/server.pem000066400000000000000000000017041333451471400210000ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd 9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 -----END CERTIFICATE----- gin-1.3.0/examples/multiple-service/000077500000000000000000000000001333451471400174045ustar00rootroot00000000000000gin-1.3.0/examples/multiple-service/main.go000066400000000000000000000021131333451471400206540ustar00rootroot00000000000000package 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 { return server01.ListenAndServe() }) g.Go(func() error { return server02.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } } gin-1.3.0/examples/realtime-chat/000077500000000000000000000000001333451471400166325ustar00rootroot00000000000000gin-1.3.0/examples/realtime-chat/Makefile000066400000000000000000000002501333451471400202670ustar00rootroot00000000000000all: deps build .PHONY: deps deps: go get -d -v github.com/dustin/go-broadcast/... .PHONY: build build: deps go build -o realtime-chat main.go rooms.go template.go gin-1.3.0/examples/realtime-chat/main.go000066400000000000000000000021201333451471400201000ustar00rootroot00000000000000package main import ( "fmt" "io" "math/rand" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.SetHTMLTemplate(html) router.GET("/room/:roomid", roomGET) router.POST("/room/:roomid", roomPOST) router.DELETE("/room/:roomid", roomDELETE) router.GET("/stream/:roomid", stream) router.Run(":8080") } func stream(c *gin.Context) { roomid := c.Param("roomid") listener := openListener(roomid) defer closeListener(roomid, listener) c.Stream(func(w io.Writer) bool { c.SSEvent("message", <-listener) return true }) } func roomGET(c *gin.Context) { roomid := c.Param("roomid") userid := fmt.Sprint(rand.Int31()) c.HTML(http.StatusOK, "chat_room", gin.H{ "roomid": roomid, "userid": userid, }) } func roomPOST(c *gin.Context) { roomid := c.Param("roomid") userid := c.PostForm("user") message := c.PostForm("message") room(roomid).Submit(userid + ": " + message) c.JSON(http.StatusOK, gin.H{ "status": "success", "message": message, }) } func roomDELETE(c *gin.Context) { roomid := c.Param("roomid") deleteBroadcast(roomid) } gin-1.3.0/examples/realtime-chat/rooms.go000066400000000000000000000012331333451471400203170ustar00rootroot00000000000000package main import "github.com/dustin/go-broadcast" var roomChannels = make(map[string]broadcast.Broadcaster) func openListener(roomid string) chan interface{} { listener := make(chan interface{}) room(roomid).Register(listener) return listener } func closeListener(roomid string, listener chan interface{}) { room(roomid).Unregister(listener) close(listener) } func deleteBroadcast(roomid string) { b, ok := roomChannels[roomid] if ok { b.Close() delete(roomChannels, roomid) } } func room(roomid string) broadcast.Broadcaster { b, ok := roomChannels[roomid] if !ok { b = broadcast.NewBroadcaster(10) roomChannels[roomid] = b } return b } gin-1.3.0/examples/realtime-chat/template.go000066400000000000000000000030021333451471400207670ustar00rootroot00000000000000package main import "html/template" var html = template.Must(template.New("chat_room").Parse(` {{.roomid}}

Welcome to {{.roomid}} room

User: Message:
`)) gin-1.3.0/examples/struct-lvl-validations/000077500000000000000000000000001333451471400205455ustar00rootroot00000000000000gin-1.3.0/examples/struct-lvl-validations/README.md000066400000000000000000000044241333451471400220300ustar00rootroot00000000000000## Struct level validations Validations can also be registered at the `struct` level when field level validations don't make much sense. This can also be used to solve cross-field validation elegantly. Additionally, it can be combined with tag validations. Struct Level validations run after the structs tag validations. ### Example requests ```shell # Validation errors are generated for struct tags as well as at the struct level $ curl -s -X POST http://localhost:8085/user \ -H 'content-type: application/json' \ -d '{}' | jq { "error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", "message": "User validation failed!" } # Validation fails at the struct level because neither first name nor last name are present $ curl -s -X POST http://localhost:8085/user \ -H 'content-type: application/json' \ -d '{"email": "george@vandaley.com"}' | jq { "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", "message": "User validation failed!" } # No validation errors when either first name or last name is present $ curl -X POST http://localhost:8085/user \ -H 'content-type: application/json' \ -d '{"fname": "George", "email": "george@vandaley.com"}' {"message":"User validation successful."} $ curl -X POST http://localhost:8085/user \ -H 'content-type: application/json' \ -d '{"lname": "Contanza", "email": "george@vandaley.com"}' {"message":"User validation successful."} $ curl -X POST http://localhost:8085/user \ -H 'content-type: application/json' \ -d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}' {"message":"User validation successful."} ``` ### Useful links - Validator docs - https://godoc.org/gopkg.in/go-playground/validator.v8#Validate.RegisterStructValidation - Struct level example - https://github.com/go-playground/validator/blob/v8.18.2/examples/struct-level/struct_level.go - Validator release notes - https://github.com/go-playground/validator/releases/tag/v8.7 gin-1.3.0/examples/struct-lvl-validations/server.go000066400000000000000000000036301333451471400224040ustar00rootroot00000000000000package main import ( "net/http" "reflect" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" validator "gopkg.in/go-playground/validator.v8" ) // User contains user information. type User struct { FirstName string `json:"fname"` LastName string `json:"lname"` Email string `binding:"required,email"` } // UserStructLevelValidation contains custom struct level validations that don't always // make sense at the field validation level. For example, this function validates that either // FirstName or LastName exist; could have done that with a custom field validation but then // would have had to add it to both fields duplicating the logic + overhead, this way it's // only validated once. // // NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way // hooks right into validator and you can combine with validation tags and still have a // common error output format. func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { user := structLevel.CurrentStruct.Interface().(User) if len(user.FirstName) == 0 && len(user.LastName) == 0 { structLevel.ReportError( reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname", ) structLevel.ReportError( reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname", ) } // plus can to more, even with different tag than "fnameorlname" } func main() { route := gin.Default() if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterStructValidation(UserStructLevelValidation, User{}) } route.POST("/user", validateUser) route.Run(":8085") } func validateUser(c *gin.Context) { var u User if err := c.ShouldBindJSON(&u); err == nil { c.JSON(http.StatusOK, gin.H{"message": "User validation successful."}) } else { c.JSON(http.StatusBadRequest, gin.H{ "message": "User validation failed!", "error": err.Error(), }) } } gin-1.3.0/examples/template/000077500000000000000000000000001333451471400157265ustar00rootroot00000000000000gin-1.3.0/examples/template/main.go000066400000000000000000000011521333451471400172000ustar00rootroot00000000000000package main 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", map[string]interface{}{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) router.Run(":8080") } gin-1.3.0/examples/upload-file/000077500000000000000000000000001333451471400163145ustar00rootroot00000000000000gin-1.3.0/examples/upload-file/multiple/000077500000000000000000000000001333451471400201475ustar00rootroot00000000000000gin-1.3.0/examples/upload-file/multiple/main.go000066400000000000000000000016401333451471400214230ustar00rootroot00000000000000package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") email := c.PostForm("email") // Multipart form form, err := c.MultipartForm() if err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) return } files := form.File["files"] for _, file := range files { if err := c.SaveUploadedFile(file, file.Filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } } c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) }) router.Run(":8080") } gin-1.3.0/examples/upload-file/multiple/public/000077500000000000000000000000001333451471400214255ustar00rootroot00000000000000gin-1.3.0/examples/upload-file/multiple/public/index.html000066400000000000000000000007071333451471400234260ustar00rootroot00000000000000 Multiple file upload

Upload multiple files with fields

Name:
Email:
Files:

gin-1.3.0/examples/upload-file/single/000077500000000000000000000000001333451471400175755ustar00rootroot00000000000000gin-1.3.0/examples/upload-file/single/main.go000066400000000000000000000015261333451471400210540ustar00rootroot00000000000000package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") email := c.PostForm("email") // Source file, err := c.FormFile("file") if err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) return } if err := c.SaveUploadedFile(file, file.Filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email)) }) router.Run(":8080") } gin-1.3.0/examples/upload-file/single/public/000077500000000000000000000000001333451471400210535ustar00rootroot00000000000000gin-1.3.0/examples/upload-file/single/public/index.html000066400000000000000000000006601333451471400230520ustar00rootroot00000000000000 Single file upload

Upload single file with fields

Name:
Email:
Files:

gin-1.3.0/fs.go000066400000000000000000000021441333451471400132350ustar00rootroot00000000000000// 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.3.0/gin.go000066400000000000000000000324551333451471400134120ustar00rootroot00000000000000// 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 ( "html/template" "net" "net/http" "os" "sync" "github.com/gin-gonic/gin/render" ) const ( // Version is Framework's version. Version = "v1.3.0" defaultMultipartMemory = 32 << 20 // 32 MB ) var ( default404Body = []byte("404 page not found") default405Body = []byte("405 method not allowed") defaultAppEngine bool ) type HandlerFunc func(*Context) type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main own. func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] } return nil } type RouteInfo struct { Method string Path string Handler string } 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 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, 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} } 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 attachs 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) 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 { routes = append(routes, RouteInfo{ Method: method, Path: path, Handler: nameOfFunction(root.handlers.Last()), }) } 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) }() os.Remove(file) listener, err := net.Listen("unix", file) if err != nil { return } defer listener.Close() 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) { c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c) } func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method path := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { path = c.Request.URL.RawPath unescape = engine.UnescapePathValues } // 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 handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if 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 handlers, _, _ := tree.root.getValue(path, nil, unescape); 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 c.Writer.Write(defaultMessage) return } c.writermem.WriteHeaderNow() return } func redirectTrailingSlash(c *Context) { req := c.Request path := req.URL.Path code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect } req.URL.Path = path + "/" if length := len(path); length > 1 && path[length-1] == '/' { req.URL.Path = path[:length-1] } debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() } func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request path := req.URL.Path if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok { code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect } req.URL.Path = string(fixedPath) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() return true } return false } gin-1.3.0/ginS/000077500000000000000000000000001333451471400131755ustar00rootroot00000000000000gin-1.3.0/ginS/README.md000066400000000000000000000003721333451471400144560ustar00rootroot00000000000000# 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.3.0/ginS/gins.go000066400000000000000000000114431333451471400144670ustar00rootroot00000000000000// 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 } func LoadHTMLGlob(pattern string) { engine().LoadHTMLGlob(pattern) } func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } 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 sets the handlers called when... TODO func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } // Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // For example, all the routes that use a common middlware for authorization could be grouped. func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } 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...) } func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } 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) } func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } // Use attachs 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...) } // Run : The router is attached 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 undefinitelly unless an error happens. func Run(addr ...string) (err error) { return engine().Run(addr...) } // RunTLS : The router is attached 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 undefinitelly unless an error happens. func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } // RunUnix : The router is attached 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 undefinitelly unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) } gin-1.3.0/gin_integration_test.go000066400000000000000000000067711333451471400170560ustar00rootroot00000000000000// 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" "fmt" "io/ioutil" "net" "net/http" "net/http/httptest" "os" "testing" "time" "github.com/stretchr/testify/assert" ) func testRequest(t *testing.T, url string) { resp, err := http.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 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() { 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 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 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") // } gin-1.3.0/gin_test.go000066400000000000000000000305241333451471400144440ustar00rootroot00000000000000// 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" "reflect" "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) func() { go func() { SetMode(mode) router := New() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") 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), }) }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { go func() { SetMode(mode) router := New() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) router.LoadHTMLGlob("./testdata/template/*") 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), }) }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } func TestLoadHTMLGlob(t *testing.T) { td := setupHTMLGlob(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLGlob2(t *testing.T) { td := setupHTMLGlob(t, TestMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLGlob3(t *testing.T) { td := setupHTMLGlob(t, ReleaseMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLGlobUsingTLS(t *testing.T) { td := setupHTMLGlob(t, DebugMode, true) // 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("https://127.0.0.1:9999/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLGlobFromFuncMap(t *testing.T) { time.Now() td := setupHTMLGlob(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/raw") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) td() } 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 TestLoadHTMLDebugMode(t *testing.T) { // router := New() // SetMode(DebugMode) // router.LoadHTMLGlob("*.testtmpl") // r := router.HTMLRender.(render.HTMLDebug) // assert.Empty(t, r.Files) // assert.Equal(t, "*.testtmpl", r.Glob) // // router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl") // r = router.HTMLRender.(render.HTMLDebug) // assert.Empty(t, r.Glob) // assert.Equal(t, []string{"index.html", "login.html"}, r.Files) // SetMode(TestMode) // } func TestLoadHTMLFiles(t *testing.T) { td := setupHTMLFiles(t, TestMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLFiles2(t *testing.T) { td := setupHTMLFiles(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLFiles3(t *testing.T) { td := setupHTMLFiles(t, ReleaseMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLFilesUsingTLS(t *testing.T) { td := setupHTMLFiles(t, TestMode, true) // 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("https://127.0.0.1:9999/test") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp[:])) td() } func TestLoadHTMLFilesFuncMap(t *testing.T) { time.Now() td := setupHTMLFiles(t, TestMode, false) res, err := http.Get("http://127.0.0.1:8888/raw") if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) td() } 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 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.3.0/githubapi_test.go000066400000000000000000000311061333451471400156400ustar00rootroot00000000000000// 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 {"GET", "/authorizations"}, {"GET", "/authorizations/:id"}, {"POST", "/authorizations"}, //{"PUT", "/authorizations/clients/:client_id"}, //{"PATCH", "/authorizations/:id"}, {"DELETE", "/authorizations/:id"}, {"GET", "/applications/:client_id/tokens/:access_token"}, {"DELETE", "/applications/:client_id/tokens"}, {"DELETE", "/applications/:client_id/tokens/:access_token"}, // Activity {"GET", "/events"}, {"GET", "/repos/:owner/:repo/events"}, {"GET", "/networks/:owner/:repo/events"}, {"GET", "/orgs/:org/events"}, {"GET", "/users/:user/received_events"}, {"GET", "/users/:user/received_events/public"}, {"GET", "/users/:user/events"}, {"GET", "/users/:user/events/public"}, {"GET", "/users/:user/events/orgs/:org"}, {"GET", "/feeds"}, {"GET", "/notifications"}, {"GET", "/repos/:owner/:repo/notifications"}, {"PUT", "/notifications"}, {"PUT", "/repos/:owner/:repo/notifications"}, {"GET", "/notifications/threads/:id"}, //{"PATCH", "/notifications/threads/:id"}, {"GET", "/notifications/threads/:id/subscription"}, {"PUT", "/notifications/threads/:id/subscription"}, {"DELETE", "/notifications/threads/:id/subscription"}, {"GET", "/repos/:owner/:repo/stargazers"}, {"GET", "/users/:user/starred"}, {"GET", "/user/starred"}, {"GET", "/user/starred/:owner/:repo"}, {"PUT", "/user/starred/:owner/:repo"}, {"DELETE", "/user/starred/:owner/:repo"}, {"GET", "/repos/:owner/:repo/subscribers"}, {"GET", "/users/:user/subscriptions"}, {"GET", "/user/subscriptions"}, {"GET", "/repos/:owner/:repo/subscription"}, {"PUT", "/repos/:owner/:repo/subscription"}, {"DELETE", "/repos/:owner/:repo/subscription"}, {"GET", "/user/subscriptions/:owner/:repo"}, {"PUT", "/user/subscriptions/:owner/:repo"}, {"DELETE", "/user/subscriptions/:owner/:repo"}, // Gists {"GET", "/users/:user/gists"}, {"GET", "/gists"}, //{"GET", "/gists/public"}, //{"GET", "/gists/starred"}, {"GET", "/gists/:id"}, {"POST", "/gists"}, //{"PATCH", "/gists/:id"}, {"PUT", "/gists/:id/star"}, {"DELETE", "/gists/:id/star"}, {"GET", "/gists/:id/star"}, {"POST", "/gists/:id/forks"}, {"DELETE", "/gists/:id"}, // Git Data {"GET", "/repos/:owner/:repo/git/blobs/:sha"}, {"POST", "/repos/:owner/:repo/git/blobs"}, {"GET", "/repos/:owner/:repo/git/commits/:sha"}, {"POST", "/repos/:owner/:repo/git/commits"}, //{"GET", "/repos/:owner/:repo/git/refs/*ref"}, {"GET", "/repos/:owner/:repo/git/refs"}, {"POST", "/repos/:owner/:repo/git/refs"}, //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"}, //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"}, {"GET", "/repos/:owner/:repo/git/tags/:sha"}, {"POST", "/repos/:owner/:repo/git/tags"}, {"GET", "/repos/:owner/:repo/git/trees/:sha"}, {"POST", "/repos/:owner/:repo/git/trees"}, // Issues {"GET", "/issues"}, {"GET", "/user/issues"}, {"GET", "/orgs/:org/issues"}, {"GET", "/repos/:owner/:repo/issues"}, {"GET", "/repos/:owner/:repo/issues/:number"}, {"POST", "/repos/:owner/:repo/issues"}, //{"PATCH", "/repos/:owner/:repo/issues/:number"}, {"GET", "/repos/:owner/:repo/assignees"}, {"GET", "/repos/:owner/:repo/assignees/:assignee"}, {"GET", "/repos/:owner/:repo/issues/:number/comments"}, //{"GET", "/repos/:owner/:repo/issues/comments"}, //{"GET", "/repos/:owner/:repo/issues/comments/:id"}, {"POST", "/repos/:owner/:repo/issues/:number/comments"}, //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"}, //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"}, {"GET", "/repos/:owner/:repo/issues/:number/events"}, //{"GET", "/repos/:owner/:repo/issues/events"}, //{"GET", "/repos/:owner/:repo/issues/events/:id"}, {"GET", "/repos/:owner/:repo/labels"}, {"GET", "/repos/:owner/:repo/labels/:name"}, {"POST", "/repos/:owner/:repo/labels"}, //{"PATCH", "/repos/:owner/:repo/labels/:name"}, {"DELETE", "/repos/:owner/:repo/labels/:name"}, {"GET", "/repos/:owner/:repo/issues/:number/labels"}, {"POST", "/repos/:owner/:repo/issues/:number/labels"}, {"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"}, {"PUT", "/repos/:owner/:repo/issues/:number/labels"}, {"DELETE", "/repos/:owner/:repo/issues/:number/labels"}, {"GET", "/repos/:owner/:repo/milestones/:number/labels"}, {"GET", "/repos/:owner/:repo/milestones"}, {"GET", "/repos/:owner/:repo/milestones/:number"}, {"POST", "/repos/:owner/:repo/milestones"}, //{"PATCH", "/repos/:owner/:repo/milestones/:number"}, {"DELETE", "/repos/:owner/:repo/milestones/:number"}, // Miscellaneous {"GET", "/emojis"}, {"GET", "/gitignore/templates"}, {"GET", "/gitignore/templates/:name"}, {"POST", "/markdown"}, {"POST", "/markdown/raw"}, {"GET", "/meta"}, {"GET", "/rate_limit"}, // Organizations {"GET", "/users/:user/orgs"}, {"GET", "/user/orgs"}, {"GET", "/orgs/:org"}, //{"PATCH", "/orgs/:org"}, {"GET", "/orgs/:org/members"}, {"GET", "/orgs/:org/members/:user"}, {"DELETE", "/orgs/:org/members/:user"}, {"GET", "/orgs/:org/public_members"}, {"GET", "/orgs/:org/public_members/:user"}, {"PUT", "/orgs/:org/public_members/:user"}, {"DELETE", "/orgs/:org/public_members/:user"}, {"GET", "/orgs/:org/teams"}, {"GET", "/teams/:id"}, {"POST", "/orgs/:org/teams"}, //{"PATCH", "/teams/:id"}, {"DELETE", "/teams/:id"}, {"GET", "/teams/:id/members"}, {"GET", "/teams/:id/members/:user"}, {"PUT", "/teams/:id/members/:user"}, {"DELETE", "/teams/:id/members/:user"}, {"GET", "/teams/:id/repos"}, {"GET", "/teams/:id/repos/:owner/:repo"}, {"PUT", "/teams/:id/repos/:owner/:repo"}, {"DELETE", "/teams/:id/repos/:owner/:repo"}, {"GET", "/user/teams"}, // Pull Requests {"GET", "/repos/:owner/:repo/pulls"}, {"GET", "/repos/:owner/:repo/pulls/:number"}, {"POST", "/repos/:owner/:repo/pulls"}, //{"PATCH", "/repos/:owner/:repo/pulls/:number"}, {"GET", "/repos/:owner/:repo/pulls/:number/commits"}, {"GET", "/repos/:owner/:repo/pulls/:number/files"}, {"GET", "/repos/:owner/:repo/pulls/:number/merge"}, {"PUT", "/repos/:owner/:repo/pulls/:number/merge"}, {"GET", "/repos/:owner/:repo/pulls/:number/comments"}, //{"GET", "/repos/:owner/:repo/pulls/comments"}, //{"GET", "/repos/:owner/:repo/pulls/comments/:number"}, {"PUT", "/repos/:owner/:repo/pulls/:number/comments"}, //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"}, //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"}, // Repositories {"GET", "/user/repos"}, {"GET", "/users/:user/repos"}, {"GET", "/orgs/:org/repos"}, {"GET", "/repositories"}, {"POST", "/user/repos"}, {"POST", "/orgs/:org/repos"}, {"GET", "/repos/:owner/:repo"}, //{"PATCH", "/repos/:owner/:repo"}, {"GET", "/repos/:owner/:repo/contributors"}, {"GET", "/repos/:owner/:repo/languages"}, {"GET", "/repos/:owner/:repo/teams"}, {"GET", "/repos/:owner/:repo/tags"}, {"GET", "/repos/:owner/:repo/branches"}, {"GET", "/repos/:owner/:repo/branches/:branch"}, {"DELETE", "/repos/:owner/:repo"}, {"GET", "/repos/:owner/:repo/collaborators"}, {"GET", "/repos/:owner/:repo/collaborators/:user"}, {"PUT", "/repos/:owner/:repo/collaborators/:user"}, {"DELETE", "/repos/:owner/:repo/collaborators/:user"}, {"GET", "/repos/:owner/:repo/comments"}, {"GET", "/repos/:owner/:repo/commits/:sha/comments"}, {"POST", "/repos/:owner/:repo/commits/:sha/comments"}, {"GET", "/repos/:owner/:repo/comments/:id"}, //{"PATCH", "/repos/:owner/:repo/comments/:id"}, {"DELETE", "/repos/:owner/:repo/comments/:id"}, {"GET", "/repos/:owner/:repo/commits"}, {"GET", "/repos/:owner/:repo/commits/:sha"}, {"GET", "/repos/:owner/:repo/readme"}, //{"GET", "/repos/:owner/:repo/contents/*path"}, //{"PUT", "/repos/:owner/:repo/contents/*path"}, //{"DELETE", "/repos/:owner/:repo/contents/*path"}, //{"GET", "/repos/:owner/:repo/:archive_format/:ref"}, {"GET", "/repos/:owner/:repo/keys"}, {"GET", "/repos/:owner/:repo/keys/:id"}, {"POST", "/repos/:owner/:repo/keys"}, //{"PATCH", "/repos/:owner/:repo/keys/:id"}, {"DELETE", "/repos/:owner/:repo/keys/:id"}, {"GET", "/repos/:owner/:repo/downloads"}, {"GET", "/repos/:owner/:repo/downloads/:id"}, {"DELETE", "/repos/:owner/:repo/downloads/:id"}, {"GET", "/repos/:owner/:repo/forks"}, {"POST", "/repos/:owner/:repo/forks"}, {"GET", "/repos/:owner/:repo/hooks"}, {"GET", "/repos/:owner/:repo/hooks/:id"}, {"POST", "/repos/:owner/:repo/hooks"}, //{"PATCH", "/repos/:owner/:repo/hooks/:id"}, {"POST", "/repos/:owner/:repo/hooks/:id/tests"}, {"DELETE", "/repos/:owner/:repo/hooks/:id"}, {"POST", "/repos/:owner/:repo/merges"}, {"GET", "/repos/:owner/:repo/releases"}, {"GET", "/repos/:owner/:repo/releases/:id"}, {"POST", "/repos/:owner/:repo/releases"}, //{"PATCH", "/repos/:owner/:repo/releases/:id"}, {"DELETE", "/repos/:owner/:repo/releases/:id"}, {"GET", "/repos/:owner/:repo/releases/:id/assets"}, {"GET", "/repos/:owner/:repo/stats/contributors"}, {"GET", "/repos/:owner/:repo/stats/commit_activity"}, {"GET", "/repos/:owner/:repo/stats/code_frequency"}, {"GET", "/repos/:owner/:repo/stats/participation"}, {"GET", "/repos/:owner/:repo/stats/punch_card"}, {"GET", "/repos/:owner/:repo/statuses/:ref"}, {"POST", "/repos/:owner/:repo/statuses/:ref"}, // Search {"GET", "/search/repositories"}, {"GET", "/search/code"}, {"GET", "/search/issues"}, {"GET", "/search/users"}, {"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"}, {"GET", "/legacy/repos/search/:keyword"}, {"GET", "/legacy/user/search/:keyword"}, {"GET", "/legacy/user/email/:email"}, // Users {"GET", "/users/:user"}, {"GET", "/user"}, //{"PATCH", "/user"}, {"GET", "/users"}, {"GET", "/user/emails"}, {"POST", "/user/emails"}, {"DELETE", "/user/emails"}, {"GET", "/users/:user/followers"}, {"GET", "/user/followers"}, {"GET", "/users/:user/following"}, {"GET", "/user/following"}, {"GET", "/user/following/:user"}, {"GET", "/users/:user/following/:target_user"}, {"PUT", "/user/following/:user"}, {"DELETE", "/user/following/:user"}, {"GET", "/users/:user/keys"}, {"GET", "/user/keys"}, {"GET", "/user/keys/:id"}, {"POST", "/user/keys"}, //{"PATCH", "/user/keys/:id"}, {"DELETE", "/user/keys/:id"}, } 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 := Default() 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, "GET", "/legacy/issues/search/:owner/:repository/:state/:keyword") } func BenchmarkParallelGithub(b *testing.B) { DefaultWriter = os.Stdout router := New() githubConfigRouter(router) req, _ := http.NewRequest("POST", "/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 := Default() githubConfigRouter(router) req, _ := http.NewRequest("POST", "/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.3.0/json/000077500000000000000000000000001333451471400132465ustar00rootroot00000000000000gin-1.3.0/json/json.go000066400000000000000000000005011333451471400145420ustar00rootroot00000000000000// 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 = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder ) gin-1.3.0/json/jsoniter.go000066400000000000000000000006141333451471400154330ustar00rootroot00000000000000// 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 = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder ) gin-1.3.0/logger.go000066400000000000000000000071131333451471400141050ustar00rootroot00000000000000// 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" ) var ( green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) reset = string([]byte{27, 91, 48, 109}) disableColor = false ) // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true } // 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 LoggerWithWriter(DefaultWriter) } // LoggerWithWriter instance a Logger middleware with the specified writter buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { isTerm := true if w, ok := out.(*os.File); !ok || (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || disableColor { 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 { // Stop timer end := time.Now() latency := end.Sub(start) clientIP := c.ClientIP() method := c.Request.Method statusCode := c.Writer.Status() var statusColor, methodColor, resetColor string if isTerm { statusColor = colorForStatus(statusCode) methodColor = colorForMethod(method) resetColor = reset } comment := c.Errors.ByType(ErrorTypePrivate).String() if raw != "" { path = path + "?" + raw } fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", end.Format("2006/01/02 - 15:04:05"), statusColor, statusCode, resetColor, latency, clientIP, methodColor, method, resetColor, path, comment, ) } } } func colorForStatus(code int) string { 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 } } func colorForMethod(method string) string { switch method { case "GET": return blue case "POST": return cyan case "PUT": return yellow case "DELETE": return red case "PATCH": return green case "HEAD": return magenta case "OPTIONS": return white default: return reset } } gin-1.3.0/logger_test.go000066400000000000000000000127521333451471400151510ustar00rootroot00000000000000// 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" "net/http" "testing" "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 TestColorForMethod(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white") assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color") } func TestColorForStatus(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { c.Error(errors.New("this is an error")) }) router.GET("/abort", func(c *Context) { c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) }) router.GET("/print", func(c *Context) { c.Error(errors.New("this is an error")) 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 TestSkippingPaths(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 TestDisableConsoleColor(t *testing.T) { New() assert.False(t, disableColor) DisableConsoleColor() assert.True(t, disableColor) } gin-1.3.0/middleware_test.go000066400000000000000000000122011333451471400157740ustar00rootroot00000000000000// 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")) }) 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.3.0/mode.go000066400000000000000000000026261333451471400135560ustar00rootroot00000000000000// 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" ) const ENV_GIN_MODE = "GIN_MODE" const ( DebugMode = "debug" ReleaseMode = "release" TestMode = "test" ) const ( debugCode = iota releaseCode testCode ) // DefaultWriter is the default io.Writer used the 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 var DefaultErrorWriter io.Writer = os.Stderr var ginMode = debugCode var modeName = DebugMode func init() { mode := os.Getenv(ENV_GIN_MODE) SetMode(mode) } 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 } func DisableBindValidation() { binding.Validator = nil } func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } func Mode() string { return modeName } gin-1.3.0/mode_test.go000066400000000000000000000020621333451471400146070ustar00rootroot00000000000000// 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(ENV_GIN_MODE, TestMode) } func TestSetMode(t *testing.T) { assert.Equal(t, testCode, ginMode) assert.Equal(t, TestMode, Mode()) os.Unsetenv(ENV_GIN_MODE) 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 TestEnableJsonDecoderUseNumber(t *testing.T) { assert.False(t, binding.EnableDecoderUseNumber) EnableJsonDecoderUseNumber() assert.True(t, binding.EnableDecoderUseNumber) } gin-1.3.0/path.go000066400000000000000000000052311333451471400135610ustar00rootroot00000000000000// 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 { // Turn empty string into "/" if p == "" { return "/" } n := len(p) var buf []byte // 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 buf = make([]byte, 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). So in contrast to the path package this // loop has no expensive function calls (except 1x make) 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 buf == nil { 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 buf == nil { return p[:w] } return string(buf[:w]) } // internal helper to lazily create a buffer if necessary. func bufApp(buf *[]byte, s string, w int, c byte) { if *buf == nil { if s[w] == c { return } *buf = make([]byte, len(s)) copy(*buf, s[:w]) } (*buf)[w] = c } gin-1.3.0/path_test.go000066400000000000000000000037201333451471400146210ustar00rootroot00000000000000// 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 ( "runtime" "testing" "github.com/stretchr/testify/assert" ) var cleanTests = []struct { path, result string }{ // 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") } if runtime.GOMAXPROCS(0) > 1 { t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") return } for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) assert.EqualValues(t, allocs, 0) } } gin-1.3.0/recovery.go000066400000000000000000000062231333451471400144650ustar00rootroot00000000000000// 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/http" "net/http/httputil" "runtime" "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 { if logger != nil { stack := stack(3) httprequest, _ := httputil.DumpRequest(c.Request, false) logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } 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.3.0/recovery_test.go000066400000000000000000000032071333451471400155230ustar00rootroot00000000000000// 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" "testing" "github.com/stretchr/testify/assert" ) // 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(), "GET /recovery") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") } // 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) } gin-1.3.0/render/000077500000000000000000000000001333451471400135545ustar00rootroot00000000000000gin-1.3.0/render/data.go000066400000000000000000000010351333451471400150130ustar00rootroot00000000000000// 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" 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 } func (r Data) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } gin-1.3.0/render/html.go000066400000000000000000000033211333451471400150460ustar00rootroot00000000000000// 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" ) type Delims struct { Left string Right string } type HTMLRender interface { Instance(string, interface{}) Render } type HTMLProduction struct { Template *template.Template Delims Delims } type HTMLDebug struct { Files []string Glob string Delims Delims FuncMap template.FuncMap } type HTML struct { Template *template.Template Name string Data interface{} } var htmlContentType = []string{"text/html; charset=utf-8"} func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, Name: name, Data: data, } } 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") } 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) } func (r HTML) WriteContentType(w http.ResponseWriter) { writeContentType(w, htmlContentType) } gin-1.3.0/render/json.go000077500000000000000000000055401333451471400150630ustar00rootroot00000000000000// 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/json" ) type JSON struct { Data interface{} } type IndentedJSON struct { Data interface{} } type SecureJSON struct { Prefix string Data interface{} } type JsonpJSON struct { Callback string Data interface{} } type AsciiJSON struct { Data interface{} } type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) } return } func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { return err } w.Write(jsonBytes) return nil } func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.MarshalIndent(r.Data, "", " ") if err != nil { return err } w.Write(jsonBytes) return nil } func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } 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, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { w.Write([]byte(r.Prefix)) } w.Write(jsonBytes) return nil } func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } 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 == "" { w.Write(ret) return nil } callback := template.JSEscapeString(r.Callback) w.Write([]byte(callback)) w.Write([]byte("(")) w.Write(ret) w.Write([]byte(")")) return nil } func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } 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 string(ret) { cvt := "" if r < 128 { cvt = string(r) } else { cvt = fmt.Sprintf("\\u%04x", int64(r)) } buffer.WriteString(cvt) } w.Write(buffer.Bytes()) return nil } func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } gin-1.3.0/render/msgpack.go000066400000000000000000000013531333451471400155320ustar00rootroot00000000000000// 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 render import ( "net/http" "github.com/ugorji/go/codec" ) type MsgPack struct { Data interface{} } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} func (r MsgPack) WriteContentType(w http.ResponseWriter) { writeContentType(w, msgpackContentType) } func (r MsgPack) Render(w http.ResponseWriter) error { return WriteMsgPack(w, r.Data) } func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) var h codec.Handle = new(codec.MsgpackHandle) return codec.NewEncoder(w, h).Encode(obj) } gin-1.3.0/render/reader.go000066400000000000000000000014251333451471400153470ustar00rootroot00000000000000package render import ( "io" "net/http" "strconv" ) 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) r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) r.writeHeaders(w, r.Headers) _, err = io.Copy(w, r.Reader) return } func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { if val := header[k]; len(val) == 0 { header[k] = []string{v} } } } gin-1.3.0/render/redirect.go000066400000000000000000000013431333451471400157050ustar00rootroot00000000000000// 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" ) type Redirect struct { Code int Request *http.Request Location string } func (r Redirect) Render(w http.ResponseWriter) error { // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // when we upgrade go version we can use http.StatusPermanentRedirect if (r.Code < 300 || r.Code > 308) && r.Code != 201 { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } http.Redirect(w, r.Request, r.Location, r.Code) return nil } func (r Redirect) WriteContentType(http.ResponseWriter) {} gin-1.3.0/render/render.go000077500000000000000000000015631333451471400153720ustar00rootroot00000000000000// 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" type Render interface { Render(http.ResponseWriter) error 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 = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} ) func writeContentType(w http.ResponseWriter, value []string) { header := w.Header() if val := header["Content-Type"]; len(val) == 0 { header["Content-Type"] = value } } gin-1.3.0/render/render_test.go000077500000000000000000000270021333451471400164250ustar00rootroot00000000000000// 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" "encoding/xml" "errors" "html/template" "net/http" "net/http/httptest" "strconv" "strings" "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")) } func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } (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\"}", 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() { (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)) } 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) } 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.Panics(t, func() { data2.Render(w) }) // 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"` 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.HeaderMap.Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } gin-1.3.0/render/text.go000066400000000000000000000013701333451471400150700ustar00rootroot00000000000000// 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" ) type String struct { Format string Data []interface{} } var plainContentType = []string{"text/plain; charset=utf-8"} func (r String) Render(w http.ResponseWriter) error { WriteString(w, r.Format, r.Data) return nil } func (r String) WriteContentType(w http.ResponseWriter) { writeContentType(w, plainContentType) } func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { fmt.Fprintf(w, format, data...) } else { io.WriteString(w, format) } } gin-1.3.0/render/xml.go000066400000000000000000000010341333451471400147010ustar00rootroot00000000000000// 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" ) type XML struct { Data interface{} } var xmlContentType = []string{"application/xml; charset=utf-8"} func (r XML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } func (r XML) WriteContentType(w http.ResponseWriter) { writeContentType(w, xmlContentType) } gin-1.3.0/render/yaml.go000066400000000000000000000011431333451471400150440ustar00rootroot00000000000000// 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" ) type YAML struct { Data interface{} } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} func (r YAML) Render(w http.ResponseWriter) error { r.WriteContentType(w) bytes, err := yaml.Marshal(r.Data) if err != nil { return err } w.Write(bytes) return nil } func (r YAML) WriteContentType(w http.ResponseWriter) { writeContentType(w, yamlContentType) } gin-1.3.0/response_writer.go000066400000000000000000000046471333451471400160710ustar00rootroot00000000000000// 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 ) type responseWriterBase 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() } 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() } gin-1.3.0/response_writer_1.7.go000066400000000000000000000004131333451471400164410ustar00rootroot00000000000000// +build !go1.8 // 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 // ResponseWriter ... type ResponseWriter interface { responseWriterBase } gin-1.3.0/response_writer_1.8.go000066400000000000000000000007621333451471400164510ustar00rootroot00000000000000// +build go1.8 // 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 import ( "net/http" ) // ResponseWriter ... type ResponseWriter interface { responseWriterBase // get the http.Pusher for server push Pusher() http.Pusher } func (w *responseWriter) Pusher() (pusher http.Pusher) { if pusher, ok := w.ResponseWriter.(http.Pusher); ok { return pusher } return nil } gin-1.3.0/response_writer_test.go000066400000000000000000000066261333451471400171270ustar00rootroot00000000000000// 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) { testWritter := httptest.NewRecorder() writer := &responseWriter{} var w ResponseWriter = writer writer.reset(testWritter) assert.Equal(t, -1, writer.size) assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, testWritter, 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) { testWritter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWritter) w := ResponseWriter(writer) w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) assert.Equal(t, http.StatusMultipleChoices, w.Status()) assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) w.WriteHeader(-1) assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { testWritter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWritter) 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, testWritter.Code) writer.size = 10 w.WriteHeaderNow() assert.Equal(t, 10, w.Size()) } func TestResponseWriterWrite(t *testing.T) { testWritter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWritter) 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, testWritter.Code) assert.Equal(t, "hola", testWritter.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", testWritter.Body.String()) assert.NoError(t, err) } func TestResponseWriterHijack(t *testing.T) { testWritter := httptest.NewRecorder() writer := &responseWriter{} writer.reset(testWritter) w := ResponseWriter(writer) assert.Panics(t, func() { w.Hijack() }) 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.3.0/routergroup.go000066400000000000000000000170261333451471400152270ustar00rootroot00000000000000// 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" ) type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } 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 middlwares or the same path prefix. // For example, all the routes that use a common middlware 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, } } 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("POST", relativePath, handlers) } // GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers) } // DELETE is a shortcut for router.Handle("DELETE", path, handle). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("DELETE", relativePath, handlers) } // PATCH is a shortcut for router.Handle("PATCH", path, handle). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PATCH", relativePath, handlers) } // PUT is a shortcut for router.Handle("PUT", path, handle). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PUT", relativePath, handlers) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("OPTIONS", relativePath, handlers) } // HEAD is a shortcut for router.Handle("HEAD", path, handle). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("HEAD", 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("GET", relativePath, handlers) group.handle("POST", relativePath, handlers) group.handle("PUT", relativePath, handlers) group.handle("PATCH", relativePath, handlers) group.handle("HEAD", relativePath, handlers) group.handle("OPTIONS", relativePath, handlers) group.handle("DELETE", relativePath, handlers) group.handle("CONNECT", relativePath, handlers) group.handle("TRACE", 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)) _, nolisting := fs.(*onlyfilesFS) return func(c *Context) { if nolisting { c.Writer.WriteHeader(http.StatusNotFound) } 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.3.0/routergroup_test.go000066400000000000000000000105611333451471400162630ustar00rootroot00000000000000// 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, "GET") performRequestInGroup(t, "POST") performRequestInGroup(t, "PUT") performRequestInGroup(t, "PATCH") performRequestInGroup(t, "DELETE") performRequestInGroup(t, "HEAD") performRequestInGroup(t, "OPTIONS") } 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 "GET": v1.GET("/test", handler) login.GET("/test", handler) case "POST": v1.POST("/test", handler) login.POST("/test", handler) case "PUT": v1.PUT("/test", handler) login.PUT("/test", handler) case "PATCH": v1.PATCH("/test", handler) login.PATCH("/test", handler) case "DELETE": v1.DELETE("/test", handler) login.DELETE("/test", handler) case "HEAD": v1.HEAD("/test", handler) login.HEAD("/test", handler) case "OPTIONS": 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("get", "/") }) 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("GET", "/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.3.0/routes_test.go000066400000000000000000000322011333451471400152020ustar00rootroot00000000000000// 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" ) func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { req, _ := http.NewRequest(method, path, nil) 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 == "POST" { methodRoute = "GET" } else { methodRoute = "POST" } 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, "PUT", "/hey") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } func TestRouterGroupRouteOK(t *testing.T) { testRouteOK("GET", t) testRouteOK("POST", t) testRouteOK("PUT", t) testRouteOK("PATCH", t) testRouteOK("HEAD", t) testRouteOK("OPTIONS", t) testRouteOK("DELETE", t) testRouteOK("CONNECT", t) testRouteOK("TRACE", t) } func TestRouteNotOK(t *testing.T) { testRouteNotOK("GET", t) testRouteNotOK("POST", t) testRouteNotOK("PUT", t) testRouteNotOK("PATCH", t) testRouteNotOK("HEAD", t) testRouteNotOK("OPTIONS", t) testRouteNotOK("DELETE", t) testRouteNotOK("CONNECT", t) testRouteNotOK("TRACE", t) } func TestRouteNotOK2(t *testing.T) { testRouteNotOK2("GET", t) testRouteNotOK2("POST", t) testRouteNotOK2("PUT", t) testRouteNotOK2("PATCH", t) testRouteNotOK2("HEAD", t) testRouteNotOK2("OPTIONS", t) testRouteNotOK2("DELETE", t) testRouteNotOK2("CONNECT", t) testRouteNotOK2("TRACE", 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, "GET", "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "PUT", "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "GET", "/path") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "GET", "/path2/") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "POST", "/path3") assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "PUT", "/path4/") assert.Equal(t, http.StatusOK, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "POST", "/path3/") assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "PUT", "/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, "GET", "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "POST", "/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, "GET", "/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()) f.WriteString("Gin Web Framework") 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, "GET", "/using_static/"+filename) w2 := performRequest(router, "GET", "/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.HeaderMap.Get("Content-Type")) w3 := performRequest(router, "HEAD", "/using_static/"+filename) w4 := performRequest(router, "HEAD", "/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, "GET", "/") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestHandleHeadToDir - ensure the root/sub dir handles properly func TestRouteStaticNoListing(t *testing.T) { router := New() router.Static("/", "./") w := performRequest(router, "GET", "/") 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, "GET", "/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.HeaderMap.Get("Content-Type")) assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires")) assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN")) } func TestRouteNotAllowedEnabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = true router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/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("POST", "/", HandlersChain{func(_ *Context) {}}) router.GET("/path2", func(c *Context) {}) w := performRequest(router, "POST", "/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, "GET", "/path") assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, http.StatusNotFound, w.Code) } 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 +/ {"", http.StatusMovedPermanently, "/"}, // 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"}, // 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"))) } } // Test custom not found handler var notFound bool router.NoRoute(func(c *Context) { c.AbortWithStatus(http.StatusNotFound) notFound = true }) w := performRequest(router, "GET", "/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, "PATCH", "/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, "GET", "/") assert.Equal(t, http.StatusNotFound, w.Code) } 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, "POST", "/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, "POST", "/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, "GET", "/NotFound") assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } gin-1.3.0/test_helpers.go000066400000000000000000000006541333451471400153320ustar00rootroot00000000000000// 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.3.0/testdata/000077500000000000000000000000001333451471400141065ustar00rootroot00000000000000gin-1.3.0/testdata/certificate/000077500000000000000000000000001333451471400163705ustar00rootroot00000000000000gin-1.3.0/testdata/certificate/cert.pem000066400000000000000000000020761333451471400200350ustar00rootroot00000000000000-----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.3.0/testdata/certificate/key.pem000066400000000000000000000032131333451471400176620ustar00rootroot00000000000000-----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.3.0/testdata/protoexample/000077500000000000000000000000001333451471400166255ustar00rootroot00000000000000gin-1.3.0/testdata/protoexample/test.pb.go000066400000000000000000000047571333451471400205500ustar00rootroot00000000000000// 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.3.0/testdata/protoexample/test.proto000066400000000000000000000003571333451471400206760ustar00rootroot00000000000000package 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.3.0/testdata/template/000077500000000000000000000000001333451471400157215ustar00rootroot00000000000000gin-1.3.0/testdata/template/hello.tmpl000066400000000000000000000000321333451471400177150ustar00rootroot00000000000000

Hello {[{.name}]}

gin-1.3.0/testdata/template/raw.tmpl000066400000000000000000000000401333451471400174020ustar00rootroot00000000000000Date: {[{.now | formatAsDate}]} gin-1.3.0/tree.go000066400000000000000000000363101333451471400135660ustar00rootroot00000000000000// 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 countParams(path string) uint8 { var n uint for i := 0; i < len(path); i++ { if path[i] != ':' && path[i] != '*' { continue } 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 } // increments priority of the given child and reorders if necessary. func (n *node) incrementChildPrio(pos int) int { n.children[pos].priority++ prio := n.children[pos].priority // adjust position (move to front) newPos := pos for newPos > 0 && n.children[newPos-1].priority < prio { // swap node positions n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1] newPos-- } // 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) // non-empty tree if len(n.path) > 0 || len(n.children) > 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 := 0 max := min(len(path), len(n.path)) for i < max && path[i] == n.path[i] { i++ } // 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, } // Update maxParams (max of all children) for i := range child.children { if child.children[i].maxParams > child.maxParams { child.maxParams = child.children[i].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 } // Make new node a child of this node if i < len(path) { path = path[i:] if n.wildChild { 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 } } panic("path segment '" + path + "' conflicts with existing wildcard '" + n.path + "' in path '" + fullPath + "'") } c := path[0] // slash after param if n.nType == param && c == '/' && len(n.children) == 1 { n = n.children[0] n.priority++ continue walk } // Check if a child with the next path byte exists for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { 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, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } n.insertChild(numParams, path, fullPath, handlers) return } else if i == len(path) { // Make node a (in-path) leaf if n.handlers != nil { panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers } return } } else { // Empty tree n.insertChild(numParams, path, fullPath, handlers) n.nType = root } } func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { var offset int // already handled bytes of the path // find prefix until first wildcard (beginning with ':' or '*') for i, max := 0, len(path); numParams > 0; i++ { c := path[i] if c != ':' && c != '*' { continue } // find wildcard end (either '/' or path end) end := i + 1 for end < max && path[end] != '/' { switch path[end] { // the wildcard name must not contain ':' and '*' case ':', '*': panic("only one wildcard per path segment is allowed, has: '" + path[i:] + "' in path '" + fullPath + "'") default: end++ } } // check if this Node existing children which would be // unreachable if we insert the wildcard here if len(n.children) > 0 { panic("wildcard route '" + path[i:end] + "' conflicts with existing children in path '" + fullPath + "'") } // check if the wildcard has a name if end-i < 2 { panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } if c == ':' { // param // split path at the beginning of the wildcard if i > 0 { n.path = path[offset:i] offset = i } child := &node{ nType: param, maxParams: numParams, } n.children = []*node{child} n.wildChild = true 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 end < max { n.path = path[offset:end] offset = end child := &node{ maxParams: numParams, priority: 1, } n.children = []*node{child} n = child } } else { // catchAll if end != max || 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[offset:i] // first node: catchAll node with empty path child := &node{ wildChild: true, nType: catchAll, maxParams: 1, } n.children = []*node{child} n.indices = string(path[i]) n = child n.priority++ // second node: node holding the variable child = &node{ path: path[i:], nType: catchAll, maxParams: 1, handlers: handlers, priority: 1, } n.children = []*node{child} return } } // insert remaining path part and handle to the leaf n.path = path[offset:] n.handlers = handlers } // 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) (handlers HandlersChain, p Params, tsr bool) { p = po walk: // Outer loop for walking the tree for { if len(path) > len(n.path) { if path[:len(n.path)] == n.path { path = path[len(n.path):] // 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] for i := 0; i < len(n.indices); i++ { if c == n.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. 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(p) < int(n.maxParams) { p = make(Params, 0, n.maxParams) } i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[1:] val := path[:end] if unescape { var err error if p[i].Value, err = url.QueryUnescape(val); err != nil { p[i].Value = val // fallback, in case of error } } else { p[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 tsr = len(path) == end+1 return } if handlers = n.handlers; handlers != nil { 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] tsr = n.path == "/" && n.handlers != nil } return case catchAll: // save param value if cap(p) < int(n.maxParams) { p = make(Params, 0, n.maxParams) } i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[2:] if unescape { var err error if p[i].Value, err = url.QueryUnescape(path); err != nil { p[i].Value = path // fallback, in case of error } } else { p[i].Value = path } handlers = n.handlers return default: panic("invalid node type") } } } else if path == n.path { // We should have reached the node containing the handle. // Check if this node has a handle registered. if handlers = n.handlers; handlers != nil { return } if path == "/" && n.wildChild && n.nType != root { tsr = true return } // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation for i := 0; i < len(n.indices); i++ { if n.indices[i] == '/' { n = n.children[i] tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) return } } return } // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path tsr = (path == "/") || (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && path == n.path[:len(n.path)-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.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { path = path[len(n.path):] ciPath = append(ciPath, n.path...) if len(path) > 0 { // 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) k := 0 for k < len(path) && path[k] != '/' { k++ } // add param value to case insensitive path ciPath = append(ciPath, path[:k]...) // we need to go deeper! if k < len(path) { if len(n.children) > 0 { path = path[k:] n = n.children[0] continue } // ... but we can't if fixTrailingSlash && len(path) == k+1 { return ciPath, true } return } if n.handlers != nil { return ciPath, true } else 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") } } else { // 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 } } // 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.ToLower(path) == strings.ToLower(n.path[:len(path)]) && n.handlers != nil { return append(ciPath, n.path...), true } } return } gin-1.3.0/tree_test.go000066400000000000000000000401431333451471400146240ustar00rootroot00000000000000// 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 ( "reflect" "strings" "testing" ) // Used as a workaround since we can't compare functions or their addressses 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 { handler, ps, _ := tree.getValue(request.path, nil, unescape) if handler == 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 { handler[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(ps, 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)) } //printChildren(tree, "") 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)) } //printChildren(tree, "") checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}}, {"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, {"/search/", false, "/search/", nil}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, {"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}}, {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}}, {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "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)) } //printChildren(tree, "") unescape := true checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file test.png"}}}, {"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file++++%%%%test.png"}}}, {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file/test.png"}}}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng in ünìcodé"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{"user", "slash/gordon"}}}, {"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash/gordon"}, Param{"project", "Project #1"}}}, {"/info/slash%%%%", false, "/info/:user", Params{Param{"user", "slash%%%%"}}}, {"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash%%%%2Fgordon"}, Param{"project", "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) } } //printChildren(tree, "") } 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) } } //printChildren(tree, "") checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, {"/user_gopher", false, "/user_:name", Params{Param{"name", "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 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) } } //printChildren(tree, "") 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 { handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !tsr { t.Errorf("expected TSR recommendation for route '%s'", route) } } noTsrRoutes := [...]string{ "/", "/no", "/no/", "/_", "/_/", "/api/world/abc", } for _, route := range noTsrRoutes { handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if 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) } handler, _, tsr := tree.getValue("/", nil, false) if handler != nil { t.Fatalf("non-nil handler") } else if 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) } } gin-1.3.0/utils.go000066400000000000000000000062711333451471400137720ustar00rootroot00000000000000// 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" ) const BindKey = "_gin-gonic/gin/bindkey" 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 // 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 // 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 much parameters") } } gin-1.3.0/utils_test.go000066400000000000000000000071171333451471400150310ustar00rootroot00000000000000// 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.3.0/vendor/000077500000000000000000000000001333451471400135725ustar00rootroot00000000000000gin-1.3.0/vendor/vendor.json000066400000000000000000000043341333451471400157660ustar00rootroot00000000000000{ "comment": "v1.2", "ignore": "test", "package": [ { "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", "comment": "v1.1.0", "path": "github.com/davecgh/go-spew/spew", "revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revisionTime": "2016-10-29T20:57:26Z" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", "revisionTime": "2017-01-09T09:34:21Z" }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", "revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revisionTime": "2017-08-29T15:58:51Z" }, { "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "path": "github.com/mattn/go-isatty", "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revisionTime": "2017-03-07T16:30:44Z" }, { "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", "comment": "v1.1.4", "path": "github.com/stretchr/testify/assert", "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revisionTime": "2017-02-15T20:11:44Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", "path": "golang.org/x/net/context", "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", "path": "gopkg.in/go-playground/validator.v8", "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", "revisionTime": "2016-07-18T13:41:25Z" }, { "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", "comment": "v2", "path": "gopkg.in/yaml.v2", "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", "revisionTime": "2016-09-28T15:37:09Z" } ], "rootPath": "github.com/gin-gonic/gin" } gin-1.3.0/wercker.yml000066400000000000000000000000241333451471400144560ustar00rootroot00000000000000box: wercker/default