Merge remote-tracking branch 'upstream/master' into upgrade_validator

This commit is contained in:
thinkerou 2018-04-20 09:37:26 +08:00
commit 6e98f2985e
74 changed files with 3461 additions and 1025 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ vendor/*
!vendor/vendor.json !vendor/vendor.json
coverage.out coverage.out
count.out count.out
test

View File

@ -4,6 +4,8 @@ go:
- 1.6.x - 1.6.x
- 1.7.x - 1.7.x
- 1.8.x - 1.8.x
- 1.9.x
- 1.10.x
- master - master
git: git:

View File

@ -1,298 +1,604 @@
**Machine:** intel i7 ivy bridge quad-core. 8GB RAM.
**Date:** June 4th, 2015 ## Benchmark System
[https://github.com/gin-gonic/go-http-routing-benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
**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
``` ```
BenchmarkAce_Param 5000000 372 ns/op 32 B/op 1 allocs/op Gin: 30512 Bytes
BenchmarkBear_Param 1000000 1165 ns/op 424 B/op 5 allocs/op
BenchmarkBeego_Param 1000000 2440 ns/op 720 B/op 10 allocs/op HttpServeMux: 17344 Bytes
BenchmarkBone_Param 1000000 1067 ns/op 384 B/op 3 allocs/op Ace: 30080 Bytes
BenchmarkDenco_Param 5000000 240 ns/op 32 B/op 1 allocs/op Bear: 30472 Bytes
BenchmarkEcho_Param 10000000 130 ns/op 0 B/op 0 allocs/op Beego: 96408 Bytes
BenchmarkGin_Param 10000000 133 ns/op 0 B/op 0 allocs/op Bone: 37904 Bytes
BenchmarkGocraftWeb_Param 1000000 1826 ns/op 656 B/op 9 allocs/op Denco: 10464 Bytes
BenchmarkGoji_Param 2000000 957 ns/op 336 B/op 2 allocs/op Echo: 73680 Bytes
BenchmarkGoJsonRest_Param 1000000 2021 ns/op 657 B/op 14 allocs/op GocraftWeb: 55720 Bytes
BenchmarkGoRestful_Param 200000 8825 ns/op 2496 B/op 31 allocs/op Goji: 27200 Bytes
BenchmarkGorillaMux_Param 500000 3340 ns/op 784 B/op 9 allocs/op Gojiv2: 104464 Bytes
BenchmarkHttpRouter_Param 10000000 152 ns/op 32 B/op 1 allocs/op GoJsonRest: 136472 Bytes
BenchmarkHttpTreeMux_Param 2000000 717 ns/op 336 B/op 2 allocs/op GoRestful: 914904 Bytes
BenchmarkKocha_Param 3000000 423 ns/op 56 B/op 3 allocs/op GorillaMux: 675568 Bytes
BenchmarkMacaron_Param 1000000 3410 ns/op 1104 B/op 11 allocs/op HttpRouter: 21128 Bytes
BenchmarkMartini_Param 200000 7101 ns/op 1152 B/op 12 allocs/op HttpTreeMux: 73448 Bytes
BenchmarkPat_Param 1000000 2040 ns/op 656 B/op 14 allocs/op Kocha: 115072 Bytes
BenchmarkPossum_Param 1000000 2048 ns/op 624 B/op 7 allocs/op LARS: 30120 Bytes
BenchmarkR2router_Param 1000000 1144 ns/op 432 B/op 6 allocs/op Macaron: 37984 Bytes
BenchmarkRevel_Param 200000 6725 ns/op 1672 B/op 28 allocs/op Martini: 310832 Bytes
BenchmarkRivet_Param 1000000 1121 ns/op 464 B/op 5 allocs/op Pat: 20464 Bytes
BenchmarkTango_Param 1000000 1479 ns/op 256 B/op 10 allocs/op Possum: 91328 Bytes
BenchmarkTigerTonic_Param 1000000 3393 ns/op 992 B/op 19 allocs/op R2router: 23712 Bytes
BenchmarkTraffic_Param 300000 5525 ns/op 1984 B/op 23 allocs/op Rivet: 23880 Bytes
BenchmarkVulcan_Param 2000000 924 ns/op 98 B/op 3 allocs/op Tango: 28008 Bytes
BenchmarkZeus_Param 1000000 1084 ns/op 368 B/op 3 allocs/op TigerTonic: 80368 Bytes
BenchmarkAce_Param5 3000000 614 ns/op 160 B/op 1 allocs/op Traffic: 626480 Bytes
BenchmarkBear_Param5 1000000 1617 ns/op 469 B/op 5 allocs/op Vulcan: 369064 Bytes
BenchmarkBeego_Param5 1000000 3373 ns/op 992 B/op 13 allocs/op ```
BenchmarkBone_Param5 1000000 1478 ns/op 432 B/op 3 allocs/op
BenchmarkDenco_Param5 3000000 570 ns/op 160 B/op 1 allocs/op ## GithubAPI Routes: 203
BenchmarkEcho_Param5 5000000 256 ns/op 0 B/op 0 allocs/op
BenchmarkGin_Param5 10000000 222 ns/op 0 B/op 0 allocs/op ```
BenchmarkGocraftWeb_Param5 1000000 2789 ns/op 928 B/op 12 allocs/op Gin: 52672 Bytes
BenchmarkGoji_Param5 1000000 1287 ns/op 336 B/op 2 allocs/op
BenchmarkGoJsonRest_Param5 1000000 3670 ns/op 1105 B/op 17 allocs/op Ace: 48992 Bytes
BenchmarkGoRestful_Param5 200000 10756 ns/op 2672 B/op 31 allocs/op Bear: 161592 Bytes
BenchmarkGorillaMux_Param5 300000 5543 ns/op 912 B/op 9 allocs/op Beego: 147992 Bytes
BenchmarkHttpRouter_Param5 5000000 403 ns/op 160 B/op 1 allocs/op Bone: 97728 Bytes
BenchmarkHttpTreeMux_Param5 1000000 1089 ns/op 336 B/op 2 allocs/op Denco: 36440 Bytes
BenchmarkKocha_Param5 1000000 1682 ns/op 440 B/op 10 allocs/op Echo: 95672 Bytes
BenchmarkMacaron_Param5 300000 4596 ns/op 1376 B/op 14 allocs/op GocraftWeb: 95640 Bytes
BenchmarkMartini_Param5 100000 15703 ns/op 1280 B/op 12 allocs/op Goji: 86088 Bytes
BenchmarkPat_Param5 300000 5320 ns/op 1008 B/op 42 allocs/op Gojiv2: 144392 Bytes
BenchmarkPossum_Param5 1000000 2155 ns/op 624 B/op 7 allocs/op GoJsonRest: 134648 Bytes
BenchmarkR2router_Param5 1000000 1559 ns/op 432 B/op 6 allocs/op GoRestful: 1410760 Bytes
BenchmarkRevel_Param5 200000 8184 ns/op 2024 B/op 35 allocs/op GorillaMux: 1509488 Bytes
BenchmarkRivet_Param5 1000000 1914 ns/op 528 B/op 9 allocs/op HttpRouter: 37464 Bytes
BenchmarkTango_Param5 1000000 3280 ns/op 944 B/op 18 allocs/op HttpTreeMux: 78800 Bytes
BenchmarkTigerTonic_Param5 200000 11638 ns/op 2519 B/op 53 allocs/op Kocha: 785408 Bytes
BenchmarkTraffic_Param5 200000 8941 ns/op 2280 B/op 31 allocs/op LARS: 49032 Bytes
BenchmarkVulcan_Param5 1000000 1279 ns/op 98 B/op 3 allocs/op Macaron: 132712 Bytes
BenchmarkZeus_Param5 1000000 1574 ns/op 416 B/op 3 allocs/op Martini: 564352 Bytes
BenchmarkAce_Param20 1000000 1528 ns/op 640 B/op 1 allocs/op Pat: 21200 Bytes
BenchmarkBear_Param20 300000 4906 ns/op 1633 B/op 5 allocs/op Possum: 83888 Bytes
BenchmarkBeego_Param20 200000 10529 ns/op 3868 B/op 17 allocs/op R2router: 47104 Bytes
BenchmarkBone_Param20 300000 7362 ns/op 2539 B/op 5 allocs/op Rivet: 42840 Bytes
BenchmarkDenco_Param20 1000000 1884 ns/op 640 B/op 1 allocs/op Tango: 54584 Bytes
BenchmarkEcho_Param20 2000000 689 ns/op 0 B/op 0 allocs/op TigerTonic: 96384 Bytes
BenchmarkGin_Param20 3000000 545 ns/op 0 B/op 0 allocs/op Traffic: 1061920 Bytes
BenchmarkGocraftWeb_Param20 200000 9437 ns/op 3804 B/op 16 allocs/op Vulcan: 465296 Bytes
BenchmarkGoji_Param20 500000 3987 ns/op 1246 B/op 2 allocs/op ```
BenchmarkGoJsonRest_Param20 100000 12799 ns/op 4492 B/op 21 allocs/op
BenchmarkGoRestful_Param20 100000 19451 ns/op 5244 B/op 33 allocs/op ## GPlusAPI Routes: 13
BenchmarkGorillaMux_Param20 100000 12456 ns/op 3275 B/op 11 allocs/op
BenchmarkHttpRouter_Param20 1000000 1333 ns/op 640 B/op 1 allocs/op ```
BenchmarkHttpTreeMux_Param20 300000 6490 ns/op 2187 B/op 4 allocs/op Gin: 3968 Bytes
BenchmarkKocha_Param20 300000 5335 ns/op 1808 B/op 27 allocs/op
BenchmarkMacaron_Param20 200000 11325 ns/op 4252 B/op 18 allocs/op Ace: 3600 Bytes
BenchmarkMartini_Param20 20000 64419 ns/op 3644 B/op 14 allocs/op Bear: 7112 Bytes
BenchmarkPat_Param20 50000 24672 ns/op 4888 B/op 151 allocs/op Beego: 10048 Bytes
BenchmarkPossum_Param20 1000000 2085 ns/op 624 B/op 7 allocs/op Bone: 6480 Bytes
BenchmarkR2router_Param20 300000 6809 ns/op 2283 B/op 8 allocs/op Denco: 3256 Bytes
BenchmarkRevel_Param20 100000 16600 ns/op 5551 B/op 54 allocs/op Echo: 9000 Bytes
BenchmarkRivet_Param20 200000 8428 ns/op 2620 B/op 26 allocs/op GocraftWeb: 7496 Bytes
BenchmarkTango_Param20 100000 16302 ns/op 8224 B/op 48 allocs/op Goji: 2912 Bytes
BenchmarkTigerTonic_Param20 30000 46828 ns/op 10538 B/op 178 allocs/op Gojiv2: 7376 Bytes
BenchmarkTraffic_Param20 50000 28871 ns/op 7998 B/op 66 allocs/op GoJsonRest: 11544 Bytes
BenchmarkVulcan_Param20 1000000 2267 ns/op 98 B/op 3 allocs/op GoRestful: 88776 Bytes
BenchmarkZeus_Param20 300000 6828 ns/op 2507 B/op 5 allocs/op GorillaMux: 71488 Bytes
BenchmarkAce_ParamWrite 3000000 502 ns/op 40 B/op 2 allocs/op HttpRouter: 2712 Bytes
BenchmarkBear_ParamWrite 1000000 1303 ns/op 424 B/op 5 allocs/op HttpTreeMux: 7440 Bytes
BenchmarkBeego_ParamWrite 1000000 2489 ns/op 728 B/op 11 allocs/op Kocha: 128880 Bytes
BenchmarkBone_ParamWrite 1000000 1181 ns/op 384 B/op 3 allocs/op LARS: 3640 Bytes
BenchmarkDenco_ParamWrite 5000000 315 ns/op 32 B/op 1 allocs/op Macaron: 8656 Bytes
BenchmarkEcho_ParamWrite 10000000 237 ns/op 8 B/op 1 allocs/op Martini: 23936 Bytes
BenchmarkGin_ParamWrite 5000000 336 ns/op 0 B/op 0 allocs/op Pat: 1856 Bytes
BenchmarkGocraftWeb_ParamWrite 1000000 2079 ns/op 664 B/op 10 allocs/op Possum: 7248 Bytes
BenchmarkGoji_ParamWrite 1000000 1092 ns/op 336 B/op 2 allocs/op R2router: 3928 Bytes
BenchmarkGoJsonRest_ParamWrite 1000000 3329 ns/op 1136 B/op 19 allocs/op Rivet: 3064 Bytes
BenchmarkGoRestful_ParamWrite 200000 9273 ns/op 2504 B/op 32 allocs/op Tango: 4912 Bytes
BenchmarkGorillaMux_ParamWrite 500000 3919 ns/op 792 B/op 10 allocs/op TigerTonic: 9408 Bytes
BenchmarkHttpRouter_ParamWrite 10000000 223 ns/op 32 B/op 1 allocs/op Traffic: 49472 Bytes
BenchmarkHttpTreeMux_ParamWrite 2000000 788 ns/op 336 B/op 2 allocs/op Vulcan: 25496 Bytes
BenchmarkKocha_ParamWrite 3000000 549 ns/op 56 B/op 3 allocs/op ```
BenchmarkMacaron_ParamWrite 500000 4558 ns/op 1216 B/op 16 allocs/op
BenchmarkMartini_ParamWrite 200000 8850 ns/op 1256 B/op 16 allocs/op ## ParseAPI Routes: 26
BenchmarkPat_ParamWrite 500000 3679 ns/op 1088 B/op 19 allocs/op
BenchmarkPossum_ParamWrite 1000000 2114 ns/op 624 B/op 7 allocs/op ```
BenchmarkR2router_ParamWrite 1000000 1320 ns/op 432 B/op 6 allocs/op Gin: 6928 Bytes
BenchmarkRevel_ParamWrite 200000 8048 ns/op 2128 B/op 33 allocs/op
BenchmarkRivet_ParamWrite 1000000 1393 ns/op 472 B/op 6 allocs/op Ace: 6592 Bytes
BenchmarkTango_ParamWrite 2000000 819 ns/op 136 B/op 5 allocs/op Bear: 12320 Bytes
BenchmarkTigerTonic_ParamWrite 300000 5860 ns/op 1440 B/op 25 allocs/op Beego: 18960 Bytes
BenchmarkTraffic_ParamWrite 200000 7429 ns/op 2400 B/op 27 allocs/op Bone: 11024 Bytes
BenchmarkVulcan_ParamWrite 2000000 972 ns/op 98 B/op 3 allocs/op Denco: 4184 Bytes
BenchmarkZeus_ParamWrite 1000000 1226 ns/op 368 B/op 3 allocs/op Echo: 11168 Bytes
BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op GocraftWeb: 12800 Bytes
BenchmarkBear_GithubStatic 3000000 575 ns/op 88 B/op 3 allocs/op Goji: 5232 Bytes
BenchmarkBeego_GithubStatic 1000000 1561 ns/op 368 B/op 7 allocs/op Gojiv2: 14464 Bytes
BenchmarkBone_GithubStatic 200000 12301 ns/op 2880 B/op 60 allocs/op GoJsonRest: 14216 Bytes
BenchmarkDenco_GithubStatic 20000000 74.6 ns/op 0 B/op 0 allocs/op GoRestful: 127368 Bytes
BenchmarkEcho_GithubStatic 10000000 176 ns/op 0 B/op 0 allocs/op GorillaMux: 123016 Bytes
BenchmarkGin_GithubStatic 10000000 159 ns/op 0 B/op 0 allocs/op HttpRouter: 4976 Bytes
BenchmarkGocraftWeb_GithubStatic 1000000 1116 ns/op 304 B/op 6 allocs/op HttpTreeMux: 7848 Bytes
BenchmarkGoji_GithubStatic 5000000 413 ns/op 0 B/op 0 allocs/op Kocha: 181712 Bytes
BenchmarkGoRestful_GithubStatic 30000 55200 ns/op 3520 B/op 36 allocs/op LARS: 6632 Bytes
BenchmarkGoJsonRest_GithubStatic 1000000 1504 ns/op 337 B/op 12 allocs/op Macaron: 13648 Bytes
BenchmarkGorillaMux_GithubStatic 100000 23620 ns/op 464 B/op 8 allocs/op Martini: 45952 Bytes
BenchmarkHttpRouter_GithubStatic 20000000 78.3 ns/op 0 B/op 0 allocs/op Pat: 2560 Bytes
BenchmarkHttpTreeMux_GithubStatic 20000000 84.9 ns/op 0 B/op 0 allocs/op Possum: 9200 Bytes
BenchmarkKocha_GithubStatic 20000000 111 ns/op 0 B/op 0 allocs/op R2router: 7056 Bytes
BenchmarkMacaron_GithubStatic 1000000 2686 ns/op 752 B/op 8 allocs/op Rivet: 5680 Bytes
BenchmarkMartini_GithubStatic 100000 22244 ns/op 832 B/op 11 allocs/op Tango: 8664 Bytes
BenchmarkPat_GithubStatic 100000 13278 ns/op 3648 B/op 76 allocs/op TigerTonic: 9840 Bytes
BenchmarkPossum_GithubStatic 1000000 1429 ns/op 480 B/op 4 allocs/op Traffic: 93480 Bytes
BenchmarkR2router_GithubStatic 2000000 726 ns/op 144 B/op 5 allocs/op Vulcan: 44504 Bytes
BenchmarkRevel_GithubStatic 300000 6271 ns/op 1288 B/op 25 allocs/op ```
BenchmarkRivet_GithubStatic 3000000 474 ns/op 112 B/op 2 allocs/op
BenchmarkTango_GithubStatic 1000000 1842 ns/op 256 B/op 10 allocs/op ## Static Routes
BenchmarkTigerTonic_GithubStatic 5000000 361 ns/op 48 B/op 1 allocs/op
BenchmarkTraffic_GithubStatic 30000 47197 ns/op 18920 B/op 149 allocs/op ```
BenchmarkVulcan_GithubStatic 1000000 1415 ns/op 98 B/op 3 allocs/op BenchmarkGin_StaticAll 50000 34506 ns/op 0 B/op 0 allocs/op
BenchmarkZeus_GithubStatic 1000000 2522 ns/op 512 B/op 11 allocs/op
BenchmarkAce_GithubParam 3000000 578 ns/op 96 B/op 1 allocs/op BenchmarkAce_StaticAll 30000 49657 ns/op 0 B/op 0 allocs/op
BenchmarkBear_GithubParam 1000000 1592 ns/op 464 B/op 5 allocs/op BenchmarkHttpServeMux_StaticAll 2000 1183737 ns/op 96 B/op 8 allocs/op
BenchmarkBeego_GithubParam 1000000 2891 ns/op 784 B/op 11 allocs/op BenchmarkBeego_StaticAll 5000 412621 ns/op 57776 B/op 628 allocs/op
BenchmarkBone_GithubParam 300000 6440 ns/op 1456 B/op 16 allocs/op BenchmarkBear_StaticAll 10000 149242 ns/op 20336 B/op 461 allocs/op
BenchmarkDenco_GithubParam 3000000 514 ns/op 128 B/op 1 allocs/op BenchmarkBone_StaticAll 10000 118583 ns/op 0 B/op 0 allocs/op
BenchmarkEcho_GithubParam 5000000 292 ns/op 0 B/op 0 allocs/op BenchmarkDenco_StaticAll 100000 13247 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GithubParam 10000000 242 ns/op 0 B/op 0 allocs/op BenchmarkEcho_StaticAll 20000 79914 ns/op 5024 B/op 157 allocs/op
BenchmarkGocraftWeb_GithubParam 1000000 2343 ns/op 720 B/op 10 allocs/op BenchmarkGocraftWeb_StaticAll 10000 211823 ns/op 46440 B/op 785 allocs/op
BenchmarkGoji_GithubParam 1000000 1566 ns/op 336 B/op 2 allocs/op BenchmarkGoji_StaticAll 10000 109390 ns/op 0 B/op 0 allocs/op
BenchmarkGoJsonRest_GithubParam 1000000 2828 ns/op 721 B/op 15 allocs/op BenchmarkGojiv2_StaticAll 3000 415533 ns/op 145696 B/op 1099 allocs/op
BenchmarkGoRestful_GithubParam 10000 177711 ns/op 2816 B/op 35 allocs/op BenchmarkGoJsonRest_StaticAll 5000 364403 ns/op 51653 B/op 1727 allocs/op
BenchmarkGorillaMux_GithubParam 100000 13591 ns/op 816 B/op 9 allocs/op BenchmarkGoRestful_StaticAll 500 2578579 ns/op 314936 B/op 3144 allocs/op
BenchmarkHttpRouter_GithubParam 5000000 352 ns/op 96 B/op 1 allocs/op BenchmarkGorillaMux_StaticAll 500 2704856 ns/op 115648 B/op 1578 allocs/op
BenchmarkHttpTreeMux_GithubParam 2000000 973 ns/op 336 B/op 2 allocs/op BenchmarkHttpRouter_StaticAll 100000 18541 ns/op 0 B/op 0 allocs/op
BenchmarkKocha_GithubParam 2000000 889 ns/op 128 B/op 5 allocs/op BenchmarkHttpTreeMux_StaticAll 100000 22332 ns/op 0 B/op 0 allocs/op
BenchmarkMacaron_GithubParam 500000 4047 ns/op 1168 B/op 12 allocs/op BenchmarkKocha_StaticAll 50000 31176 ns/op 0 B/op 0 allocs/op
BenchmarkMartini_GithubParam 50000 28982 ns/op 1184 B/op 12 allocs/op BenchmarkLARS_StaticAll 50000 40840 ns/op 0 B/op 0 allocs/op
BenchmarkPat_GithubParam 200000 8747 ns/op 2480 B/op 56 allocs/op BenchmarkMacaron_StaticAll 5000 517656 ns/op 120576 B/op 1413 allocs/op
BenchmarkPossum_GithubParam 1000000 2158 ns/op 624 B/op 7 allocs/op BenchmarkMartini_StaticAll 300 4462289 ns/op 125442 B/op 1717 allocs/op
BenchmarkR2router_GithubParam 1000000 1352 ns/op 432 B/op 6 allocs/op BenchmarkPat_StaticAll 500 2157275 ns/op 533904 B/op 11123 allocs/op
BenchmarkRevel_GithubParam 200000 7673 ns/op 1784 B/op 30 allocs/op BenchmarkPossum_StaticAll 10000 254701 ns/op 65312 B/op 471 allocs/op
BenchmarkRivet_GithubParam 1000000 1573 ns/op 480 B/op 6 allocs/op BenchmarkR2router_StaticAll 10000 133956 ns/op 22608 B/op 628 allocs/op
BenchmarkTango_GithubParam 1000000 2418 ns/op 480 B/op 13 allocs/op BenchmarkRivet_StaticAll 30000 46812 ns/op 0 B/op 0 allocs/op
BenchmarkTigerTonic_GithubParam 300000 6048 ns/op 1440 B/op 28 allocs/op BenchmarkTango_StaticAll 5000 390613 ns/op 39225 B/op 1256 allocs/op
BenchmarkTraffic_GithubParam 100000 20143 ns/op 6024 B/op 55 allocs/op BenchmarkTigerTonic_StaticAll 20000 88060 ns/op 7504 B/op 157 allocs/op
BenchmarkVulcan_GithubParam 1000000 2224 ns/op 98 B/op 3 allocs/op BenchmarkTraffic_StaticAll 500 2910236 ns/op 729736 B/op 14287 allocs/op
BenchmarkZeus_GithubParam 500000 4156 ns/op 1312 B/op 12 allocs/op BenchmarkVulcan_StaticAll 5000 277366 ns/op 15386 B/op 471 allocs/op
BenchmarkAce_GithubAll 10000 109482 ns/op 13792 B/op 167 allocs/op ```
BenchmarkBear_GithubAll 10000 287490 ns/op 79952 B/op 943 allocs/op
BenchmarkBeego_GithubAll 3000 562184 ns/op 146272 B/op 2092 allocs/op ## Micro Benchmarks
BenchmarkBone_GithubAll 500 2578716 ns/op 648016 B/op 8119 allocs/op
BenchmarkDenco_GithubAll 20000 94955 ns/op 20224 B/op 167 allocs/op ```
BenchmarkEcho_GithubAll 30000 58705 ns/op 0 B/op 0 allocs/op BenchmarkGin_Param 20000000 113 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GithubAll 30000 50991 ns/op 0 B/op 0 allocs/op
BenchmarkGocraftWeb_GithubAll 5000 449648 ns/op 133280 B/op 1889 allocs/op BenchmarkAce_Param 5000000 375 ns/op 32 B/op 1 allocs/op
BenchmarkGoji_GithubAll 2000 689748 ns/op 56113 B/op 334 allocs/op BenchmarkBear_Param 1000000 1709 ns/op 456 B/op 5 allocs/op
BenchmarkGoJsonRest_GithubAll 5000 537769 ns/op 135995 B/op 2940 allocs/op BenchmarkBeego_Param 1000000 2484 ns/op 368 B/op 4 allocs/op
BenchmarkGoRestful_GithubAll 100 18410628 ns/op 797236 B/op 7725 allocs/op BenchmarkBone_Param 1000000 2391 ns/op 688 B/op 5 allocs/op
BenchmarkGorillaMux_GithubAll 200 8036360 ns/op 153137 B/op 1791 allocs/op BenchmarkDenco_Param 10000000 240 ns/op 32 B/op 1 allocs/op
BenchmarkHttpRouter_GithubAll 20000 63506 ns/op 13792 B/op 167 allocs/op BenchmarkEcho_Param 5000000 366 ns/op 32 B/op 1 allocs/op
BenchmarkHttpTreeMux_GithubAll 10000 165927 ns/op 56112 B/op 334 allocs/op BenchmarkGocraftWeb_Param 1000000 2343 ns/op 648 B/op 8 allocs/op
BenchmarkKocha_GithubAll 10000 171362 ns/op 23304 B/op 843 allocs/op BenchmarkGoji_Param 1000000 1197 ns/op 336 B/op 2 allocs/op
BenchmarkMacaron_GithubAll 2000 817008 ns/op 224960 B/op 2315 allocs/op BenchmarkGojiv2_Param 1000000 2771 ns/op 944 B/op 8 allocs/op
BenchmarkMartini_GithubAll 100 12609209 ns/op 237952 B/op 2686 allocs/op BenchmarkGoJsonRest_Param 1000000 2993 ns/op 649 B/op 13 allocs/op
BenchmarkPat_GithubAll 300 4830398 ns/op 1504101 B/op 32222 allocs/op BenchmarkGoRestful_Param 200000 8860 ns/op 2296 B/op 21 allocs/op
BenchmarkPossum_GithubAll 10000 301716 ns/op 97440 B/op 812 allocs/op BenchmarkGorillaMux_Param 500000 4461 ns/op 1056 B/op 11 allocs/op
BenchmarkR2router_GithubAll 10000 270691 ns/op 77328 B/op 1182 allocs/op BenchmarkHttpRouter_Param 10000000 175 ns/op 32 B/op 1 allocs/op
BenchmarkRevel_GithubAll 1000 1491919 ns/op 345553 B/op 5918 allocs/op BenchmarkHttpTreeMux_Param 1000000 1167 ns/op 352 B/op 3 allocs/op
BenchmarkRivet_GithubAll 10000 283860 ns/op 84272 B/op 1079 allocs/op BenchmarkKocha_Param 3000000 429 ns/op 56 B/op 3 allocs/op
BenchmarkTango_GithubAll 5000 473821 ns/op 87078 B/op 2470 allocs/op BenchmarkLARS_Param 10000000 134 ns/op 0 B/op 0 allocs/op
BenchmarkTigerTonic_GithubAll 2000 1120131 ns/op 241088 B/op 6052 allocs/op BenchmarkMacaron_Param 500000 4635 ns/op 1056 B/op 10 allocs/op
BenchmarkTraffic_GithubAll 200 8708979 ns/op 2664762 B/op 22390 allocs/op BenchmarkMartini_Param 200000 9933 ns/op 1072 B/op 10 allocs/op
BenchmarkVulcan_GithubAll 5000 353392 ns/op 19894 B/op 609 allocs/op BenchmarkPat_Param 1000000 2929 ns/op 648 B/op 12 allocs/op
BenchmarkZeus_GithubAll 2000 944234 ns/op 300688 B/op 2648 allocs/op BenchmarkPossum_Param 1000000 2503 ns/op 560 B/op 6 allocs/op
BenchmarkAce_GPlusStatic 5000000 251 ns/op 0 B/op 0 allocs/op BenchmarkR2router_Param 1000000 1507 ns/op 432 B/op 5 allocs/op
BenchmarkBear_GPlusStatic 3000000 415 ns/op 72 B/op 3 allocs/op BenchmarkRivet_Param 5000000 297 ns/op 48 B/op 1 allocs/op
BenchmarkBeego_GPlusStatic 1000000 1416 ns/op 352 B/op 7 allocs/op BenchmarkTango_Param 1000000 1862 ns/op 248 B/op 8 allocs/op
BenchmarkBone_GPlusStatic 10000000 192 ns/op 32 B/op 1 allocs/op BenchmarkTigerTonic_Param 500000 5660 ns/op 992 B/op 17 allocs/op
BenchmarkDenco_GPlusStatic 30000000 47.6 ns/op 0 B/op 0 allocs/op BenchmarkTraffic_Param 200000 8408 ns/op 1960 B/op 21 allocs/op
BenchmarkEcho_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op BenchmarkVulcan_Param 2000000 963 ns/op 98 B/op 3 allocs/op
BenchmarkGin_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op BenchmarkAce_Param5 2000000 740 ns/op 160 B/op 1 allocs/op
BenchmarkGocraftWeb_GPlusStatic 1000000 1035 ns/op 288 B/op 6 allocs/op BenchmarkBear_Param5 1000000 2777 ns/op 501 B/op 5 allocs/op
BenchmarkGoji_GPlusStatic 5000000 304 ns/op 0 B/op 0 allocs/op BenchmarkBeego_Param5 1000000 3740 ns/op 368 B/op 4 allocs/op
BenchmarkGoJsonRest_GPlusStatic 1000000 1286 ns/op 337 B/op 12 allocs/op BenchmarkBone_Param5 1000000 2950 ns/op 736 B/op 5 allocs/op
BenchmarkGoRestful_GPlusStatic 200000 9649 ns/op 2160 B/op 30 allocs/op BenchmarkDenco_Param5 2000000 644 ns/op 160 B/op 1 allocs/op
BenchmarkGorillaMux_GPlusStatic 1000000 2346 ns/op 464 B/op 8 allocs/op BenchmarkEcho_Param5 3000000 558 ns/op 32 B/op 1 allocs/op
BenchmarkHttpRouter_GPlusStatic 30000000 42.7 ns/op 0 B/op 0 allocs/op BenchmarkGin_Param5 10000000 198 ns/op 0 B/op 0 allocs/op
BenchmarkHttpTreeMux_GPlusStatic 30000000 49.5 ns/op 0 B/op 0 allocs/op BenchmarkGocraftWeb_Param5 500000 3870 ns/op 920 B/op 11 allocs/op
BenchmarkKocha_GPlusStatic 20000000 74.8 ns/op 0 B/op 0 allocs/op BenchmarkGoji_Param5 1000000 1746 ns/op 336 B/op 2 allocs/op
BenchmarkMacaron_GPlusStatic 1000000 2520 ns/op 736 B/op 8 allocs/op BenchmarkGojiv2_Param5 1000000 3214 ns/op 1008 B/op 8 allocs/op
BenchmarkMartini_GPlusStatic 300000 5310 ns/op 832 B/op 11 allocs/op BenchmarkGoJsonRest_Param5 500000 5509 ns/op 1097 B/op 16 allocs/op
BenchmarkPat_GPlusStatic 5000000 398 ns/op 96 B/op 2 allocs/op BenchmarkGoRestful_Param5 200000 11232 ns/op 2392 B/op 21 allocs/op
BenchmarkPossum_GPlusStatic 1000000 1434 ns/op 480 B/op 4 allocs/op BenchmarkGorillaMux_Param5 300000 7777 ns/op 1184 B/op 11 allocs/op
BenchmarkR2router_GPlusStatic 2000000 646 ns/op 144 B/op 5 allocs/op BenchmarkHttpRouter_Param5 3000000 631 ns/op 160 B/op 1 allocs/op
BenchmarkRevel_GPlusStatic 300000 6172 ns/op 1272 B/op 25 allocs/op BenchmarkHttpTreeMux_Param5 1000000 2800 ns/op 576 B/op 6 allocs/op
BenchmarkRivet_GPlusStatic 3000000 444 ns/op 112 B/op 2 allocs/op BenchmarkKocha_Param5 1000000 2053 ns/op 440 B/op 10 allocs/op
BenchmarkTango_GPlusStatic 1000000 1400 ns/op 208 B/op 10 allocs/op BenchmarkLARS_Param5 10000000 232 ns/op 0 B/op 0 allocs/op
BenchmarkTigerTonic_GPlusStatic 10000000 213 ns/op 32 B/op 1 allocs/op BenchmarkMacaron_Param5 500000 5888 ns/op 1056 B/op 10 allocs/op
BenchmarkTraffic_GPlusStatic 1000000 3091 ns/op 1208 B/op 16 allocs/op BenchmarkMartini_Param5 200000 12807 ns/op 1232 B/op 11 allocs/op
BenchmarkVulcan_GPlusStatic 2000000 863 ns/op 98 B/op 3 allocs/op BenchmarkPat_Param5 300000 7320 ns/op 964 B/op 32 allocs/op
BenchmarkZeus_GPlusStatic 10000000 237 ns/op 16 B/op 1 allocs/op BenchmarkPossum_Param5 1000000 2495 ns/op 560 B/op 6 allocs/op
BenchmarkAce_GPlusParam 3000000 435 ns/op 64 B/op 1 allocs/op BenchmarkR2router_Param5 1000000 1844 ns/op 432 B/op 5 allocs/op
BenchmarkBear_GPlusParam 1000000 1205 ns/op 448 B/op 5 allocs/op BenchmarkRivet_Param5 2000000 935 ns/op 240 B/op 1 allocs/op
BenchmarkBeego_GPlusParam 1000000 2494 ns/op 720 B/op 10 allocs/op BenchmarkTango_Param5 1000000 2327 ns/op 360 B/op 8 allocs/op
BenchmarkBone_GPlusParam 1000000 1126 ns/op 384 B/op 3 allocs/op BenchmarkTigerTonic_Param5 100000 18514 ns/op 2551 B/op 43 allocs/op
BenchmarkDenco_GPlusParam 5000000 325 ns/op 64 B/op 1 allocs/op BenchmarkTraffic_Param5 200000 11997 ns/op 2248 B/op 25 allocs/op
BenchmarkEcho_GPlusParam 10000000 168 ns/op 0 B/op 0 allocs/op BenchmarkVulcan_Param5 1000000 1333 ns/op 98 B/op 3 allocs/op
BenchmarkGin_GPlusParam 10000000 170 ns/op 0 B/op 0 allocs/op BenchmarkAce_Param20 1000000 2031 ns/op 640 B/op 1 allocs/op
BenchmarkGocraftWeb_GPlusParam 1000000 1895 ns/op 656 B/op 9 allocs/op BenchmarkBear_Param20 200000 7285 ns/op 1664 B/op 5 allocs/op
BenchmarkGoji_GPlusParam 1000000 1071 ns/op 336 B/op 2 allocs/op BenchmarkBeego_Param20 300000 6224 ns/op 368 B/op 4 allocs/op
BenchmarkGoJsonRest_GPlusParam 1000000 2282 ns/op 657 B/op 14 allocs/op BenchmarkBone_Param20 200000 8023 ns/op 1903 B/op 5 allocs/op
BenchmarkGoRestful_GPlusParam 100000 19400 ns/op 2560 B/op 33 allocs/op BenchmarkDenco_Param20 1000000 2262 ns/op 640 B/op 1 allocs/op
BenchmarkGorillaMux_GPlusParam 500000 5001 ns/op 784 B/op 9 allocs/op BenchmarkEcho_Param20 1000000 1387 ns/op 32 B/op 1 allocs/op
BenchmarkHttpRouter_GPlusParam 10000000 240 ns/op 64 B/op 1 allocs/op BenchmarkGin_Param20 3000000 503 ns/op 0 B/op 0 allocs/op
BenchmarkHttpTreeMux_GPlusParam 2000000 797 ns/op 336 B/op 2 allocs/op BenchmarkGocraftWeb_Param20 100000 14408 ns/op 3795 B/op 15 allocs/op
BenchmarkKocha_GPlusParam 3000000 505 ns/op 56 B/op 3 allocs/op BenchmarkGoji_Param20 500000 5272 ns/op 1247 B/op 2 allocs/op
BenchmarkMacaron_GPlusParam 1000000 3668 ns/op 1104 B/op 11 allocs/op BenchmarkGojiv2_Param20 1000000 4163 ns/op 1248 B/op 8 allocs/op
BenchmarkMartini_GPlusParam 200000 10672 ns/op 1152 B/op 12 allocs/op BenchmarkGoJsonRest_Param20 100000 17866 ns/op 4485 B/op 20 allocs/op
BenchmarkPat_GPlusParam 1000000 2376 ns/op 704 B/op 14 allocs/op BenchmarkGoRestful_Param20 100000 21022 ns/op 4724 B/op 23 allocs/op
BenchmarkPossum_GPlusParam 1000000 2090 ns/op 624 B/op 7 allocs/op BenchmarkGorillaMux_Param20 100000 17055 ns/op 3547 B/op 13 allocs/op
BenchmarkR2router_GPlusParam 1000000 1233 ns/op 432 B/op 6 allocs/op BenchmarkHttpRouter_Param20 1000000 1748 ns/op 640 B/op 1 allocs/op
BenchmarkRevel_GPlusParam 200000 6778 ns/op 1704 B/op 28 allocs/op BenchmarkHttpTreeMux_Param20 200000 12246 ns/op 3196 B/op 10 allocs/op
BenchmarkRivet_GPlusParam 1000000 1279 ns/op 464 B/op 5 allocs/op BenchmarkKocha_Param20 300000 6861 ns/op 1808 B/op 27 allocs/op
BenchmarkTango_GPlusParam 1000000 1981 ns/op 272 B/op 10 allocs/op BenchmarkLARS_Param20 3000000 526 ns/op 0 B/op 0 allocs/op
BenchmarkTigerTonic_GPlusParam 500000 3893 ns/op 1064 B/op 19 allocs/op BenchmarkMacaron_Param20 100000 13069 ns/op 2906 B/op 12 allocs/op
BenchmarkTraffic_GPlusParam 200000 6585 ns/op 2000 B/op 23 allocs/op BenchmarkMartini_Param20 100000 23602 ns/op 3597 B/op 13 allocs/op
BenchmarkVulcan_GPlusParam 1000000 1233 ns/op 98 B/op 3 allocs/op BenchmarkPat_Param20 50000 32143 ns/op 4688 B/op 111 allocs/op
BenchmarkZeus_GPlusParam 1000000 1350 ns/op 368 B/op 3 allocs/op BenchmarkPossum_Param20 1000000 2396 ns/op 560 B/op 6 allocs/op
BenchmarkAce_GPlus2Params 3000000 512 ns/op 64 B/op 1 allocs/op BenchmarkR2router_Param20 200000 8907 ns/op 2283 B/op 7 allocs/op
BenchmarkBear_GPlus2Params 1000000 1564 ns/op 464 B/op 5 allocs/op BenchmarkRivet_Param20 1000000 3280 ns/op 1024 B/op 1 allocs/op
BenchmarkBeego_GPlus2Params 1000000 3043 ns/op 784 B/op 11 allocs/op BenchmarkTango_Param20 500000 4640 ns/op 856 B/op 8 allocs/op
BenchmarkBone_GPlus2Params 1000000 3152 ns/op 736 B/op 7 allocs/op BenchmarkTigerTonic_Param20 20000 67581 ns/op 10532 B/op 138 allocs/op
BenchmarkDenco_GPlus2Params 3000000 431 ns/op 64 B/op 1 allocs/op BenchmarkTraffic_Param20 50000 40313 ns/op 7941 B/op 45 allocs/op
BenchmarkEcho_GPlus2Params 5000000 247 ns/op 0 B/op 0 allocs/op BenchmarkVulcan_Param20 1000000 2264 ns/op 98 B/op 3 allocs/op
BenchmarkGin_GPlus2Params 10000000 219 ns/op 0 B/op 0 allocs/op BenchmarkAce_ParamWrite 3000000 532 ns/op 40 B/op 2 allocs/op
BenchmarkGocraftWeb_GPlus2Params 1000000 2363 ns/op 720 B/op 10 allocs/op BenchmarkBear_ParamWrite 1000000 1778 ns/op 456 B/op 5 allocs/op
BenchmarkGoji_GPlus2Params 1000000 1540 ns/op 336 B/op 2 allocs/op BenchmarkBeego_ParamWrite 1000000 2596 ns/op 376 B/op 5 allocs/op
BenchmarkGoJsonRest_GPlus2Params 1000000 2872 ns/op 721 B/op 15 allocs/op BenchmarkBone_ParamWrite 1000000 2519 ns/op 688 B/op 5 allocs/op
BenchmarkGoRestful_GPlus2Params 100000 23030 ns/op 2720 B/op 35 allocs/op BenchmarkDenco_ParamWrite 5000000 411 ns/op 32 B/op 1 allocs/op
BenchmarkGorillaMux_GPlus2Params 200000 10516 ns/op 816 B/op 9 allocs/op BenchmarkEcho_ParamWrite 2000000 718 ns/op 40 B/op 2 allocs/op
BenchmarkHttpRouter_GPlus2Params 5000000 273 ns/op 64 B/op 1 allocs/op BenchmarkGin_ParamWrite 5000000 283 ns/op 0 B/op 0 allocs/op
BenchmarkHttpTreeMux_GPlus2Params 2000000 939 ns/op 336 B/op 2 allocs/op BenchmarkGocraftWeb_ParamWrite 1000000 2561 ns/op 656 B/op 9 allocs/op
BenchmarkKocha_GPlus2Params 2000000 844 ns/op 128 B/op 5 allocs/op BenchmarkGoji_ParamWrite 1000000 1378 ns/op 336 B/op 2 allocs/op
BenchmarkMacaron_GPlus2Params 500000 3914 ns/op 1168 B/op 12 allocs/op BenchmarkGojiv2_ParamWrite 1000000 3128 ns/op 976 B/op 10 allocs/op
BenchmarkMartini_GPlus2Params 50000 35759 ns/op 1280 B/op 16 allocs/op BenchmarkGoJsonRest_ParamWrite 500000 4446 ns/op 1128 B/op 18 allocs/op
BenchmarkPat_GPlus2Params 200000 7089 ns/op 2304 B/op 41 allocs/op BenchmarkGoRestful_ParamWrite 200000 10291 ns/op 2304 B/op 22 allocs/op
BenchmarkPossum_GPlus2Params 1000000 2093 ns/op 624 B/op 7 allocs/op BenchmarkGorillaMux_ParamWrite 500000 5153 ns/op 1064 B/op 12 allocs/op
BenchmarkR2router_GPlus2Params 1000000 1320 ns/op 432 B/op 6 allocs/op BenchmarkHttpRouter_ParamWrite 5000000 263 ns/op 32 B/op 1 allocs/op
BenchmarkRevel_GPlus2Params 200000 7351 ns/op 1800 B/op 30 allocs/op BenchmarkHttpTreeMux_ParamWrite 1000000 1351 ns/op 352 B/op 3 allocs/op
BenchmarkRivet_GPlus2Params 1000000 1485 ns/op 480 B/op 6 allocs/op BenchmarkKocha_ParamWrite 3000000 538 ns/op 56 B/op 3 allocs/op
BenchmarkTango_GPlus2Params 1000000 2111 ns/op 448 B/op 12 allocs/op BenchmarkLARS_ParamWrite 5000000 316 ns/op 0 B/op 0 allocs/op
BenchmarkTigerTonic_GPlus2Params 300000 6271 ns/op 1528 B/op 28 allocs/op BenchmarkMacaron_ParamWrite 500000 5756 ns/op 1160 B/op 14 allocs/op
BenchmarkTraffic_GPlus2Params 100000 14886 ns/op 3312 B/op 34 allocs/op BenchmarkMartini_ParamWrite 200000 13097 ns/op 1176 B/op 14 allocs/op
BenchmarkVulcan_GPlus2Params 1000000 1883 ns/op 98 B/op 3 allocs/op BenchmarkPat_ParamWrite 500000 4954 ns/op 1072 B/op 17 allocs/op
BenchmarkZeus_GPlus2Params 1000000 2686 ns/op 784 B/op 6 allocs/op BenchmarkPossum_ParamWrite 1000000 2499 ns/op 560 B/op 6 allocs/op
BenchmarkAce_GPlusAll 300000 5912 ns/op 640 B/op 11 allocs/op BenchmarkR2router_ParamWrite 1000000 1531 ns/op 432 B/op 5 allocs/op
BenchmarkBear_GPlusAll 100000 16448 ns/op 5072 B/op 61 allocs/op BenchmarkRivet_ParamWrite 3000000 570 ns/op 112 B/op 2 allocs/op
BenchmarkBeego_GPlusAll 50000 32916 ns/op 8976 B/op 129 allocs/op BenchmarkTango_ParamWrite 2000000 957 ns/op 136 B/op 4 allocs/op
BenchmarkBone_GPlusAll 50000 25836 ns/op 6992 B/op 76 allocs/op BenchmarkTigerTonic_ParamWrite 200000 7025 ns/op 1424 B/op 23 allocs/op
BenchmarkDenco_GPlusAll 500000 4462 ns/op 672 B/op 11 allocs/op BenchmarkTraffic_ParamWrite 200000 10112 ns/op 2384 B/op 25 allocs/op
BenchmarkEcho_GPlusAll 500000 2806 ns/op 0 B/op 0 allocs/op BenchmarkVulcan_ParamWrite 1000000 1006 ns/op 98 B/op 3 allocs/op
BenchmarkGin_GPlusAll 500000 2579 ns/op 0 B/op 0 allocs/op ```
BenchmarkGocraftWeb_GPlusAll 50000 25223 ns/op 8144 B/op 116 allocs/op
BenchmarkGoji_GPlusAll 100000 14237 ns/op 3696 B/op 22 allocs/op ## GitHub
BenchmarkGoJsonRest_GPlusAll 50000 29227 ns/op 8221 B/op 183 allocs/op
BenchmarkGoRestful_GPlusAll 10000 203144 ns/op 36064 B/op 441 allocs/op ```
BenchmarkGorillaMux_GPlusAll 20000 80906 ns/op 9712 B/op 115 allocs/op BenchmarkGin_GithubStatic 10000000 156 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlusAll 500000 3040 ns/op 640 B/op 11 allocs/op
BenchmarkHttpTreeMux_GPlusAll 200000 9627 ns/op 3696 B/op 22 allocs/op BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op
BenchmarkKocha_GPlusAll 200000 8108 ns/op 976 B/op 43 allocs/op BenchmarkBear_GithubStatic 2000000 893 ns/op 120 B/op 3 allocs/op
BenchmarkMacaron_GPlusAll 30000 48083 ns/op 13968 B/op 142 allocs/op BenchmarkBeego_GithubStatic 1000000 2491 ns/op 368 B/op 4 allocs/op
BenchmarkMartini_GPlusAll 10000 196978 ns/op 15072 B/op 178 allocs/op BenchmarkBone_GithubStatic 50000 25300 ns/op 2880 B/op 60 allocs/op
BenchmarkPat_GPlusAll 30000 58865 ns/op 16880 B/op 343 allocs/op BenchmarkDenco_GithubStatic 20000000 76.0 ns/op 0 B/op 0 allocs/op
BenchmarkPossum_GPlusAll 100000 19685 ns/op 6240 B/op 52 allocs/op BenchmarkEcho_GithubStatic 2000000 516 ns/op 32 B/op 1 allocs/op
BenchmarkR2router_GPlusAll 100000 16251 ns/op 5040 B/op 76 allocs/op BenchmarkGocraftWeb_GithubStatic 1000000 1448 ns/op 296 B/op 5 allocs/op
BenchmarkRevel_GPlusAll 20000 93489 ns/op 21656 B/op 368 allocs/op BenchmarkGoji_GithubStatic 3000000 496 ns/op 0 B/op 0 allocs/op
BenchmarkRivet_GPlusAll 100000 16907 ns/op 5408 B/op 64 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
``` ```

View File

@ -2,14 +2,14 @@ GOFMT ?= gofmt "-s"
PACKAGES ?= $(shell go list ./... | grep -v /vendor/) PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
all: build all: install
install: deps install: deps
govendor sync govendor sync
.PHONY: test .PHONY: test
test: test:
go test -v -covermode=count -coverprofile=coverage.out sh coverage.sh
.PHONY: fmt .PHONY: fmt
fmt: fmt:

453
README.md
View File

@ -7,6 +7,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/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) [![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) [![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)
[![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 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.
@ -44,41 +45,40 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
[See all benchmarks](/BENCHMARKS.md) [See all benchmarks](/BENCHMARKS.md)
Benchmark name | (1) | (2) | (3) | (4) Benchmark name | (1) | (2) | (3) | (4)
--------------------------------|----------:|----------:|----------:|------: --------------------------------------------|-----------:|------------:|-----------:|---------:
BenchmarkAce_GithubAll | 10000 | 109482 | 13792 | 167 **BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0**
BenchmarkBear_GithubAll | 10000 | 287490 | 79952 | 943 BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167
BenchmarkBeego_GithubAll | 3000 | 562184 | 146272 | 2092 BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943
BenchmarkBone_GithubAll | 500 | 2578716 | 648016 | 8119 BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812
BenchmarkDenco_GithubAll | 20000 | 94955 | 20224 | 167 BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453
BenchmarkEcho_GithubAll | 30000 | 58705 | 0 | 0 BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167
**BenchmarkGin_GithubAll** | **30000** | **50991** | **0** | **0** BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203
BenchmarkGocraftWeb_GithubAll | 5000 | 449648 | 133280 | 1889 BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686
BenchmarkGoji_GithubAll | 2000 | 689748 | 56113 | 334 BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334
BenchmarkGoJsonRest_GithubAll | 5000 | 537769 | 135995 | 2940 BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712
BenchmarkGoRestful_GithubAll | 100 | 18410628 | 797236 | 7725 BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737
BenchmarkGorillaMux_GithubAll | 200 | 8036360 | 153137 | 1791 BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519
BenchmarkHttpRouter_GithubAll | 20000 | 63506 | 13792 | 167 BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272
BenchmarkHttpTreeMux_GithubAll | 10000 | 165927 | 56112 | 334 BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167
BenchmarkKocha_GithubAll | 10000 | 171362 | 23304 | 843 BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671
BenchmarkMacaron_GithubAll | 2000 | 817008 | 224960 | 2315 BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843
BenchmarkMartini_GithubAll | 100 | 12609209 | 237952 | 2686 BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0
BenchmarkPat_GithubAll | 300 | 4830398 | 1504101 | 32222 BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000
BenchmarkPossum_GithubAll | 10000 | 301716 | 97440 | 812 BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325
BenchmarkR2router_GithubAll | 10000 | 270691 | 77328 | 1182 BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435
BenchmarkRevel_GithubAll | 1000 | 1491919 | 345553 | 5918 BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609
BenchmarkRivet_GithubAll | 10000 | 283860 | 84272 | 1079 BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979
BenchmarkTango_GithubAll | 5000 | 473821 | 87078 | 2470 BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167
BenchmarkTigerTonic_GithubAll | 2000 | 1120131 | 241088 | 6052 BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618
BenchmarkTraffic_GithubAll | 200 | 8708979 | 2664762 | 22390 BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374
BenchmarkVulcan_GithubAll | 5000 | 353392 | 19894 | 609 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
(1): Total Repetitions - (1): Total Repetitions achieved in constant time, higher means more confident result
(2): Single Repetition Duration (ns/op) - (2): Single Repetition Duration (ns/op), lower is better
(3): Heap Memory (B/op) - (3): Heap Memory (B/op), lower is better
(4): Average Allocations per Repetition (allocs/op) - (4): Average Allocations per Repetition (allocs/op), lower is better
## Gin v1. stable ## Gin v1. stable
@ -88,7 +88,6 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
- [x] Battle tested - [x] Battle tested
- [x] API frozen, new releases will not break your code. - [x] API frozen, new releases will not break your code.
## Start using it ## Start using it
1. Download and install it: 1. Download and install it:
@ -119,7 +118,7 @@ $ go get github.com/kardianos/govendor
2. Create your project folder and `cd` inside 2. Create your project folder and `cd` inside
```sh ```sh
$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
``` ```
3. Vendor init your project and add gin 3. Vendor init your project and add gin
@ -141,6 +140,14 @@ $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/mai
$ go run main.go $ go run main.go
``` ```
## 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 ## API Examples
### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
@ -271,12 +278,17 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail
```go ```go
func main() { func main() {
router := gin.Default() 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) { router.POST("/upload", func(c *gin.Context) {
// single file // single file
file, _ := c.FormFile("file") file, _ := c.FormFile("file")
log.Println(file.Filename) log.Println(file.Filename)
c.String(http.StatusOK, fmt.Printf("'%s' uploaded!", 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") router.Run(":8080")
} }
@ -297,6 +309,8 @@ See the detail [example code](examples/upload-file/multiple).
```go ```go
func main() { func main() {
router := gin.Default() 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) { router.POST("/upload", func(c *gin.Context) {
// Multipart form // Multipart form
form, _ := c.MultipartForm() form, _ := c.MultipartForm()
@ -304,8 +318,11 @@ func main() {
for _, file := range files { for _, file := range files {
log.Println(file.Filename) log.Println(file.Filename)
// Upload the file to specific dst.
// c.SaveUploadedFile(file, dst)
} }
c.String(http.StatusOK, fmt.Printf("%d files uploaded!", len(files))) c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
}) })
router.Run(":8080") router.Run(":8080")
} }
@ -357,6 +374,7 @@ r := gin.New()
instead of instead of
```go ```go
// Default With the Logger and Recovery middleware already attached
r := gin.Default() r := gin.Default()
``` ```
@ -368,7 +386,11 @@ func main() {
r := gin.New() r := gin.New()
// Global middleware // 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()) r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery()) r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire. // Per route middleware, you can add as many as you desire.
@ -396,15 +418,47 @@ func main() {
} }
``` ```
### 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 ### 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). 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"`. 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"`.
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 BindWith. 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.
You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, the current request will fail with an error. 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 ```go
// Binding from JSON // Binding from JSON
@ -419,12 +473,14 @@ func main() {
// Example for binding JSON ({"user": "manu", "password": "123"}) // Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) { router.POST("/loginJSON", func(c *gin.Context) {
var json Login var json Login
if c.BindJSON(&json) == nil { if err := c.ShouldBindJSON(&json); err == nil {
if json.User == "manu" && json.Password == "123" { if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else { } else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
} }
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} }
}) })
@ -432,12 +488,14 @@ func main() {
router.POST("/loginForm", func(c *gin.Context) { router.POST("/loginForm", func(c *gin.Context) {
var form Login var form Login
// This will infer what binder to use depending on the content-type header. // This will infer what binder to use depending on the content-type header.
if c.Bind(&form) == nil { if err := c.ShouldBind(&form); err == nil {
if form.User == "manu" && form.Password == "123" { if form.User == "manu" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else { } else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
} }
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} }
}) })
@ -446,7 +504,133 @@ func main() {
} }
``` ```
### Bind Query String **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"}
```
### 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). See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
@ -455,10 +639,12 @@ package main
import "log" import "log"
import "github.com/gin-gonic/gin" import "github.com/gin-gonic/gin"
import "time"
type Person struct { type Person struct {
Name string `form:"name"` Name string `form:"name"`
Address string `form:"address"` Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
} }
func main() { func main() {
@ -472,15 +658,21 @@ func startPage(c *gin.Context) {
// If `GET`, only `Form` binding engine (`query`) used. // If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // 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 // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
if c.Bind(&person) == nil { if c.ShouldBind(&person) == nil {
log.Println(person.Name) log.Println(person.Name)
log.Println(person.Address) log.Println(person.Address)
log.Println(person.Birthday)
} }
c.String(200, "Success") 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 ### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
@ -498,7 +690,7 @@ type myForm struct {
func formHandler(c *gin.Context) { func formHandler(c *gin.Context) {
var fakeForm myForm var fakeForm myForm
c.Bind(&fakeForm) c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors}) c.JSON(200, gin.H{"color": fakeForm.Colors})
} }
@ -545,11 +737,11 @@ func main() {
router := gin.Default() router := gin.Default()
router.POST("/login", func(c *gin.Context) { router.POST("/login", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration: // you can bind multipart form with explicit binding declaration:
// c.MustBindWith(&form, binding.Form) // c.ShouldBindWith(&form, binding.Form)
// or you can simply use autobinding with Bind method: // or you can simply use autobinding with ShouldBind method:
var form LoginForm var form LoginForm
// in this case proper binding will be automatically selected // in this case proper binding will be automatically selected
if c.Bind(&form) == nil { if c.ShouldBind(&form) == nil {
if form.User == "user" && form.Password == "password" { if form.User == "user" && form.Password == "password" {
c.JSON(200, gin.H{"status": "you are logged in"}) c.JSON(200, gin.H{"status": "you are logged in"})
} else { } else {
@ -741,31 +933,42 @@ You may use custom delims
#### Custom Template Funcs #### Custom Template Funcs
See the detail [example code](examples/template).
main.go main.go
```go ```go
... import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func formatAsDate(t time.Time) string { func formatAsDate(t time.Time) string {
year, month, day := t.Date() year, month, day := t.Date()
return fmt.Sprintf("%d/%02d/%02d", year, month, day) return fmt.Sprintf("%d%02d/%02d", year, month, day)
} }
... func main() {
router := gin.Default()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate, "formatAsDate": formatAsDate,
}) })
router.LoadHTMLFiles("./fixtures/basic/raw.tmpl")
... router.GET("/raw", func(c *gin.Context) {
router.GET("/raw", func(c *Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
}) })
}) })
... router.Run(":8080")
}
``` ```
raw.tmpl raw.tmpl
@ -876,7 +1079,7 @@ func main() {
### Goroutines inside a middleware ### Goroutines inside a middleware
When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. 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 ```go
func main() { func main() {
@ -938,7 +1141,7 @@ func main() {
example for 1-line LetsEncrypt HTTPS servers. example for 1-line LetsEncrypt HTTPS servers.
[embedmd]:# (examples/auto-tls/example1.go go) [embedmd]:# (examples/auto-tls/example1/main.go go)
```go ```go
package main package main
@ -963,7 +1166,7 @@ func main() {
example for custom autocert manager. example for custom autocert manager.
[embedmd]:# (examples/auto-tls/example2.go go) [embedmd]:# (examples/auto-tls/example2/main.go go)
```go ```go
package main package main
@ -993,6 +1196,88 @@ func main() {
} }
``` ```
### 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 ### Graceful restart or stop
Do you want to graceful restart or stop your web server? Do you want to graceful restart or stop your web server?
@ -1046,8 +1331,8 @@ func main() {
go func() { go func() {
// service connections // service connections
if err := srv.ListenAndServe(); err != nil { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("listen: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
}() }()
@ -1063,7 +1348,53 @@ func main() {
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err) log.Fatal("Server Shutdown:", err)
} }
log.Println("Server exist") log.Println("Server exiting")
}
```
## 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())
} }
``` ```

33
auth.go
View File

@ -10,26 +10,26 @@ import (
"strconv" "strconv"
) )
// AuthUserKey is the cookie name for user credential in basic auth // AuthUserKey is the cookie name for user credential in basic auth.
const AuthUserKey = "user" const AuthUserKey = "user"
// Accounts defines a key/value for user/pass list of authorized logins // Accounts defines a key/value for user/pass list of authorized logins.
type Accounts map[string]string type Accounts map[string]string
type authPair struct { type authPair struct {
Value string value string
User string user string
} }
type authPairs []authPair type authPairs []authPair
func (a authPairs) searchCredential(authValue string) (string, bool) { func (a authPairs) searchCredential(authValue string) (string, bool) {
if len(authValue) == 0 { if authValue == "" {
return "", false return "", false
} }
for _, pair := range a { for _, pair := range a {
if pair.Value == authValue { if pair.value == authValue {
return pair.User, true return pair.user, true
} }
} }
return "", false return "", false
@ -47,16 +47,17 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
pairs := processAccounts(accounts) pairs := processAccounts(accounts)
return func(c *Context) { return func(c *Context) {
// Search user in the slice of allowed credentials // Search user in the slice of allowed credentials
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization")) user, found := pairs.searchCredential(c.requestHeader("Authorization"))
if !found { if !found {
// Credentials doesn't match, we return 401 and abort handlers chain. // Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm) c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(401) c.AbortWithStatus(401)
} else { return
// The user credentials was found, set user's id to key AuthUserKey in this context, the userId can be read later using
// c.MustGet(gin.AuthUserKey)
c.Set(AuthUserKey, user)
} }
// 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)
} }
} }
@ -70,11 +71,11 @@ func processAccounts(accounts Accounts) authPairs {
assert1(len(accounts) > 0, "Empty list of authorized credentials") assert1(len(accounts) > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, len(accounts)) pairs := make(authPairs, 0, len(accounts))
for user, password := range accounts { for user, password := range accounts {
assert1(len(user) > 0, "User can not be empty") assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password) value := authorizationHeader(user, password)
pairs = append(pairs, authPair{ pairs = append(pairs, authPair{
Value: value, value: value,
User: user, user: user,
}) })
} }
return pairs return pairs
@ -89,6 +90,6 @@ func secureCompare(given, actual string) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
} }
// Securely compare actual to itself to keep constant time, but always return false // Securely compare actual to itself to keep constant time, but always return false.
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
} }

View File

@ -22,16 +22,16 @@ func TestBasicAuth(t *testing.T) {
assert.Len(t, pairs, 3) assert.Len(t, pairs, 3)
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "bar", user: "bar",
Value: "Basic YmFyOmZvbw==", value: "Basic YmFyOmZvbw==",
}) })
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "foo", user: "foo",
Value: "Basic Zm9vOmJhcg==", value: "Basic Zm9vOmJhcg==",
}) })
assert.Contains(t, pairs, authPair{ assert.Contains(t, pairs, authPair{
User: "admin", user: "admin",
Value: "Basic YWRtaW46cGFzc3dvcmQ=", value: "Basic YWRtaW46cGFzc3dvcmQ=",
}) })
} }
@ -53,15 +53,15 @@ func TestBasicAuthSearchCredential(t *testing.T) {
}) })
user, found := pairs.searchCredential(authorizationHeader("admin", "password")) user, found := pairs.searchCredential(authorizationHeader("admin", "password"))
assert.Equal(t, user, "admin") assert.Equal(t, "admin", user)
assert.True(t, found) assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("foo", "bar")) user, found = pairs.searchCredential(authorizationHeader("foo", "bar"))
assert.Equal(t, user, "foo") assert.Equal(t, "foo", user)
assert.True(t, found) assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("bar", "foo")) user, found = pairs.searchCredential(authorizationHeader("bar", "foo"))
assert.Equal(t, user, "bar") assert.Equal(t, "bar", user)
assert.True(t, found) assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("admins", "password")) user, found = pairs.searchCredential(authorizationHeader("admins", "password"))
@ -78,7 +78,7 @@ func TestBasicAuthSearchCredential(t *testing.T) {
} }
func TestBasicAuthAuthorizationHeader(t *testing.T) { func TestBasicAuthAuthorizationHeader(t *testing.T) {
assert.Equal(t, authorizationHeader("admin", "password"), "Basic YWRtaW46cGFzc3dvcmQ=") assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password"))
} }
func TestBasicAuthSecureCompare(t *testing.T) { func TestBasicAuthSecureCompare(t *testing.T) {
@ -101,8 +101,8 @@ func TestBasicAuthSucceed(t *testing.T) {
req.Header.Set("Authorization", authorizationHeader("admin", "password")) req.Header.Set("Authorization", authorizationHeader("admin", "password"))
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Equal(t, w.Body.String(), "admin") assert.Equal(t, "admin", w.Body.String())
} }
func TestBasicAuth401(t *testing.T) { func TestBasicAuth401(t *testing.T) {
@ -121,8 +121,8 @@ func TestBasicAuth401(t *testing.T) {
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.False(t, called) assert.False(t, called)
assert.Equal(t, w.Code, 401) assert.Equal(t, 401, w.Code)
assert.Equal(t, w.HeaderMap.Get("WWW-Authenticate"), "Basic realm=\"Authorization Required\"") assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate"))
} }
func TestBasicAuth401WithCustomRealm(t *testing.T) { func TestBasicAuth401WithCustomRealm(t *testing.T) {
@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.False(t, called) assert.False(t, called)
assert.Equal(t, w.Code, 401) assert.Equal(t, 401, w.Code)
assert.Equal(t, w.HeaderMap.Get("WWW-Authenticate"), "Basic realm=\"My Custom \\\"Realm\\\"\"") assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate"))
} }

View File

@ -59,8 +59,6 @@ func BenchmarkOneRouteJSON(B *testing.B) {
runRequest(B, router, "GET", "/json") runRequest(B, router, "GET", "/json")
} }
var htmlContentType = []string{"text/html; charset=utf-8"}
func BenchmarkOneRouteHTML(B *testing.B) { func BenchmarkOneRouteHTML(B *testing.B) {
router := New() router := New()
t := template.Must(template.New("index").Parse(` t := template.Must(template.New("index").Parse(`

View File

@ -4,7 +4,9 @@
package binding package binding
import "net/http" import (
"net/http"
)
const ( const (
MIMEJSON = "application/json" MIMEJSON = "application/json"
@ -19,11 +21,18 @@ const (
MIMEMSGPACK2 = "application/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 { type Binding interface {
Name() string Name() string
Bind(*http.Request, interface{}) error Bind(*http.Request, 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 { type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // 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 not a struct, any validation should be skipped and nil must be returned.
@ -31,20 +40,32 @@ type StructValidator interface {
// If the struct is not valid or the validation itself fails, a descriptive error should be returned. // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned. // Otherwise nil must be returned.
ValidateStruct(interface{}) error 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{} 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 ( var (
JSON = jsonBinding{} JSON = jsonBinding{}
XML = xmlBinding{} XML = xmlBinding{}
Form = formBinding{} Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{} FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{} FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{} ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{} MsgPack = msgpackBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method
// and the content type.
func Default(method, contentType string) Binding { func Default(method, contentType string) Binding {
if method == "GET" { if method == "GET" {
return Form return Form

View File

@ -6,9 +6,13 @@ package binding
import ( import (
"bytes" "bytes"
"encoding/json"
"errors"
"io/ioutil"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"testing" "testing"
"time"
"github.com/gin-gonic/gin/binding/example" "github.com/gin-gonic/gin/binding/example"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -25,6 +29,116 @@ type FooBarStruct struct {
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` Bar string `msgpack:"bar" json:"bar" form:"bar" 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 FooStructForSliceMapType struct {
// Unknown type: not support map
SliceMapFoo []map[string]interface{} `form:"slice_map_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"`
}
func TestBindingDefault(t *testing.T) { func TestBindingDefault(t *testing.T) {
assert.Equal(t, Default("GET", ""), Form) assert.Equal(t, Default("GET", ""), Form)
assert.Equal(t, Default("GET", MIMEJSON), Form) assert.Equal(t, Default("GET", MIMEJSON), Form)
@ -55,6 +169,20 @@ func TestBindingJSON(t *testing.T) {
`{"foo": "bar"}`, `{"bar": "foo"}`) `{"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) { func TestBindingForm(t *testing.T) {
testFormBinding(t, "POST", testFormBinding(t, "POST",
"/", "/", "/", "/",
@ -67,6 +195,198 @@ func TestBindingForm2(t *testing.T) {
"", "") "", "")
} }
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")
}
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 TestBindingXML(t *testing.T) { func TestBindingXML(t *testing.T) {
testBodyBinding(t, testBodyBinding(t,
XML, "xml", XML, "xml",
@ -74,12 +394,25 @@ func TestBindingXML(t *testing.T) {
"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>") "<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
} }
func TestBindingXMLFail(t *testing.T) {
testBodyBindingFail(t,
XML, "xml",
"/", "/",
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
}
func createFormPostRequest() *http.Request { func createFormPostRequest() *http.Request {
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", MIMEPOSTForm)
return req 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 { func createFormMultipartRequest() *http.Request {
boundary := "--testboundary" boundary := "--testboundary"
body := new(bytes.Buffer) body := new(bytes.Buffer)
@ -94,24 +427,53 @@ func createFormMultipartRequest() *http.Request {
return req 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) { func TestBindingFormPost(t *testing.T) {
req := createFormPostRequest() req := createFormPostRequest()
var obj FooBarStruct var obj FooBarStruct
FormPost.Bind(req, &obj) FormPost.Bind(req, &obj)
assert.Equal(t, FormPost.Name(), "form-urlencoded")
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Foo, "bar")
assert.Equal(t, obj.Bar, "foo") assert.Equal(t, obj.Bar, "foo")
} }
func TestBindingFormPostFail(t *testing.T) {
req := createFormPostRequestFail()
var obj FooStructForMapType
err := FormPost.Bind(req, &obj)
assert.Error(t, err)
}
func TestBindingFormMultipart(t *testing.T) { func TestBindingFormMultipart(t *testing.T) {
req := createFormMultipartRequest() req := createFormMultipartRequest()
var obj FooBarStruct var obj FooBarStruct
FormMultipart.Bind(req, &obj) FormMultipart.Bind(req, &obj)
assert.Equal(t, FormMultipart.Name(), "multipart/form-data")
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Foo, "bar")
assert.Equal(t, obj.Bar, "foo") assert.Equal(t, obj.Bar, "foo")
} }
func TestBindingFormMultipartFail(t *testing.T) {
req := createFormMultipartRequestFail()
var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj)
assert.Error(t, err)
}
func TestBindingProtoBuf(t *testing.T) { func TestBindingProtoBuf(t *testing.T) {
test := &example.Test{ test := &example.Test{
Label: proto.String("yes"), Label: proto.String("yes"),
@ -124,6 +486,18 @@ func TestBindingProtoBuf(t *testing.T) {
string(data), string(data[1:])) string(data), string(data[1:]))
} }
func TestBindingProtoBufFail(t *testing.T) {
test := &example.Test{
Label: proto.String("yes"),
}
data, _ := proto.Marshal(test)
testProtoBodyBindingFail(t,
ProtoBuf, "protobuf",
"/", "/",
string(data), string(data[1:]))
}
func TestBindingMsgPack(t *testing.T) { func TestBindingMsgPack(t *testing.T) {
test := FooStruct{ test := FooStruct{
Foo: "bar", Foo: "bar",
@ -204,6 +578,351 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string)
assert.Error(t, err) assert.Error(t, err)
} }
func TestFormBindingFail(t *testing.T) {
b := Form
assert.Equal(t, b.Name(), "form")
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, b.Name(), "form-urlencoded")
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, b.Name(), "multipart/form-data")
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, b.Name(), "form")
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, obj.TimeFoo.Unix(), int64(1510675200))
assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing")
assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800))
assert.Equal(t, obj.TimeBar.Location().String(), "UTC")
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, b.Name(), "form")
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, b.Name(), "form")
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, b.Name(), "form")
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, b.Name(), "form")
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, b.Name(), "form")
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, b.Name(), "form")
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, obj.IntFoo, int(0))
assert.Equal(t, obj.IntBar, int(-12))
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, obj.Int8Foo, int8(0))
assert.Equal(t, obj.Int8Bar, int8(-12))
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, obj.Int16Foo, int16(0))
assert.Equal(t, obj.Int16Bar, int16(-12))
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, obj.Int32Foo, int32(0))
assert.Equal(t, obj.Int32Bar, int32(-12))
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, obj.Int64Foo, int64(0))
assert.Equal(t, obj.Int64Bar, int64(-12))
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, obj.UintFoo, uint(0x0))
assert.Equal(t, obj.UintBar, uint(0xc))
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, obj.Uint8Foo, uint8(0x0))
assert.Equal(t, obj.Uint8Bar, uint8(0xc))
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, obj.Uint16Foo, uint16(0x0))
assert.Equal(t, obj.Uint16Bar, uint16(0xc))
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, obj.Uint32Foo, uint32(0x0))
assert.Equal(t, obj.Uint32Bar, uint32(0xc))
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, obj.Uint64Foo, uint64(0x0))
assert.Equal(t, obj.Uint64Bar, uint64(0xc))
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, obj.Float32Foo, float32(0.0))
assert.Equal(t, obj.Float32Bar, float32(-12.34))
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, obj.Float64Foo, float64(0.0))
assert.Equal(t, obj.Float64Bar, float64(-12.34))
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.Equal(t, obj.BoolFoo, false)
assert.Equal(t, obj.BoolBar, true)
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, obj.SliceFoo, []int{1, 2})
obj = FooStructForSliceType{}
req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj)
assert.Error(t, err)
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)
}
}
func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) {
b := Query
assert.Equal(t, b.Name(), "query")
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, obj.Foo, "bar")
assert.Equal(t, obj.Bar, "foo")
}
func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) {
b := Query
assert.Equal(t, b.Name(), "query")
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 testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, b.Name(), name) assert.Equal(t, b.Name(), name)
@ -219,6 +938,58 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, b.Name(), 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, v, int64(123))
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, b.Name(), 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, obj.Foo, float64(123))
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, b.Name(), 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) { func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, b.Name(), name) assert.Equal(t, b.Name(), name)
@ -236,6 +1007,30 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba
assert.Error(t, err) 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, b.Name(), name)
obj := example.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 = example.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) { func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, b.Name(), name) assert.Equal(t, b.Name(), name)

View File

@ -28,6 +28,15 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
return nil 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() { func (v *defaultValidator) lazyinit() {
v.once.Do(func() { v.once.Do(func() {
v.validate = validator.New() v.validate = validator.New()

View File

@ -6,6 +6,8 @@ package binding
import "net/http" import "net/http"
const defaultMemory = 32 * 1024 * 1024
type formBinding struct{} type formBinding struct{}
type formPostBinding struct{} type formPostBinding struct{}
type formMultipartBinding struct{} type formMultipartBinding struct{}
@ -18,7 +20,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil { if err := req.ParseForm(); err != nil {
return err return err
} }
req.ParseMultipartForm(32 << 10) // 32 MB req.ParseMultipartForm(defaultMemory)
if err := mapForm(obj, req.Form); err != nil { if err := mapForm(obj, req.Form); err != nil {
return err return err
} }
@ -44,7 +46,7 @@ func (formMultipartBinding) Name() string {
} }
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseMultipartForm(32 << 10); err != nil { if err := req.ParseMultipartForm(defaultMemory); err != nil {
return err return err
} }
if err := mapForm(obj, req.MultipartForm.Value); err != nil { if err := mapForm(obj, req.MultipartForm.Value); err != nil {

View File

@ -163,6 +163,14 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
l = time.UTC 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) t, err := time.ParseInLocation(timeFormat, val, l)
if err != nil { if err != nil {
return err return err
@ -171,12 +179,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
value.Set(reflect.ValueOf(t)) value.Set(reflect.ValueOf(t))
return nil return nil
} }
// Don't pass in pointers to bind to. Can lead to bugs. See:
// https://github.com/codegangsta/martini-contrib/issues/40
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
func ensureNotPointer(obj interface{}) {
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
panic("Pointers are not accepted as binding models")
}
}

View File

@ -7,13 +7,13 @@ package binding
import ( import (
"net/http" "net/http"
"github.com/json-iterator/go" "github.com/gin-gonic/gin/json"
) )
var ( // EnableDecoderUseNumber is used to call the UseNumber method on the JSON
json = jsoniter.ConfigCompatibleWithStandardLibrary // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
EnableDecoderUseNumber = false // interface{} as a Number instead of as a float64.
) var EnableDecoderUseNumber = false
type jsonBinding struct{} type jsonBinding struct{}

View File

@ -17,12 +17,8 @@ func (msgpackBinding) Name() string {
} }
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil {
//var decoder *codec.Decoder = codec.NewDecoder(req.Body, &codec.MsgpackHandle)
//if err := decoder.Decode(&obj); err != nil {
return err return err
} }
return validate(obj) return validate(obj)
} }

21
binding/query.go Normal file
View File

@ -0,0 +1,21 @@
// 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)
}

View File

@ -6,10 +6,12 @@ package binding
import ( import (
"bytes" "bytes"
"reflect"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/go-playground/validator.v8"
) )
type testInterface interface { type testInterface interface {
@ -190,3 +192,45 @@ func TestValidatePrimitives(t *testing.T) {
assert.NoError(t, validate(&str)) assert.NoError(t, validate(&str))
assert.Equal(t, str, "value") assert.Equal(t, str, "value")
} }
// 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")
}

View File

@ -13,6 +13,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"time" "time"
@ -21,7 +22,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Content-Type MIME of the most common data formats // Content-Type MIME of the most common data formats.
const ( const (
MIMEJSON = binding.MIMEJSON MIMEJSON = binding.MIMEJSON
MIMEHTML = binding.MIMEHTML MIMEHTML = binding.MIMEHTML
@ -32,10 +33,7 @@ const (
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
) )
const ( const abortIndex int8 = math.MaxInt8 / 2
defaultMemory = 32 << 20 // 32 MB
abortIndex int8 = math.MaxInt8 / 2
)
// Context is the most important part of gin. It allows us to pass variables between middleware, // 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. // manage the flow, validate the JSON of a request and render a JSON response for example.
@ -50,13 +48,13 @@ type Context struct {
engine *Engine engine *Engine
// Keys is a key/value pair exclusively for the context of each request // Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{} Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context // Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation // Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string Accepted []string
} }
@ -86,7 +84,7 @@ func (c *Context) Copy() *Context {
} }
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", // HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
// this function will return "main.handleGetUsers" // this function will return "main.handleGetUsers".
func (c *Context) HandlerName() string { func (c *Context) HandlerName() string {
return nameOfFunction(c.handlers.Last()) return nameOfFunction(c.handlers.Last())
} }
@ -105,8 +103,7 @@ func (c *Context) Handler() HandlerFunc {
// See example in GitHub. // See example in GitHub.
func (c *Context) Next() { func (c *Context) Next() {
c.index++ c.index++
s := int8(len(c.handlers)) for s := int8(len(c.handlers)); c.index < s; c.index++ {
for ; c.index < s; c.index++ {
c.handlers[c.index](c) c.handlers[c.index](c)
} }
} }
@ -357,8 +354,7 @@ func (c *Context) QueryArray(key string) []string {
// GetQueryArray returns a slice of strings for a given query key, plus // 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. // a boolean value whether at least one value exists for the given key.
func (c *Context) GetQueryArray(key string) ([]string, bool) { func (c *Context) GetQueryArray(key string) ([]string, bool) {
req := c.Request if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 {
if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
return values, true return values, true
} }
return []string{}, false return []string{}, false
@ -407,7 +403,7 @@ func (c *Context) PostFormArray(key string) []string {
func (c *Context) GetPostFormArray(key string) ([]string, bool) { func (c *Context) GetPostFormArray(key string) ([]string, bool) {
req := c.Request req := c.Request
req.ParseForm() req.ParseForm()
req.ParseMultipartForm(defaultMemory) req.ParseMultipartForm(c.engine.MaxMultipartMemory)
if values := req.PostForm[key]; len(values) > 0 { if values := req.PostForm[key]; len(values) > 0 {
return values, true return values, true
} }
@ -427,30 +423,53 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
// MultipartForm is the parsed multipart form, including file uploads. // MultipartForm is the parsed multipart form, including file uploads.
func (c *Context) MultipartForm() (*multipart.Form, error) { func (c *Context) MultipartForm() (*multipart.Form, error) {
err := c.Request.ParseMultipartForm(defaultMemory) err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
return c.Request.MultipartForm, err 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, // Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used: // Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding // "application/json" --> JSON binding
// "application/xml" --> XML binding // "application/xml" --> XML binding
// otherwise --> returns an error // 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 parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer. // It decodes the json payload into the struct specified as a pointer.
// Like ParseBody() but this method also writes a 400 error if the json is not valid. // 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 { func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType()) b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b) return c.MustBindWith(obj, b)
} }
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj interface{}) error { func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON)
} }
// MustBindWith binds the passed struct pointer using the specified binding // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
// engine. It will abort the request with HTTP 400 if any error ocurrs. 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. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil { if err = c.ShouldBindWith(obj, b); err != nil {
@ -460,8 +479,30 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
return return
} }
// ShouldBindWith binds the passed struct pointer using the specified binding // ShouldBind checks the Content-Type to select a binding engine automatically,
// engine. // 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. // See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj) return b.Bind(c.Request, obj)
@ -477,17 +518,17 @@ func (c *Context) ClientIP() string {
clientIP = clientIP[0:index] clientIP = clientIP[0:index]
} }
clientIP = strings.TrimSpace(clientIP) clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 { if clientIP != "" {
return clientIP return clientIP
} }
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
if len(clientIP) > 0 { if clientIP != "" {
return clientIP return clientIP
} }
} }
if c.engine.AppEngine { if c.engine.AppEngine {
if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" { if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
return addr return addr
} }
} }
@ -515,17 +556,14 @@ func (c *Context) IsWebsocket() bool {
} }
func (c *Context) requestHeader(key string) string { func (c *Context) requestHeader(key string) string {
if values, _ := c.Request.Header[key]; len(values) > 0 { return c.Request.Header.Get(key)
return values[0]
}
return ""
} }
/************************************/ /************************************/
/******** RESPONSE RENDERING ********/ /******** RESPONSE RENDERING ********/
/************************************/ /************************************/
// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus(status int) bool { func bodyAllowedForStatus(status int) bool {
switch { switch {
case status >= 100 && status <= 199: case status >= 100 && status <= 199:
@ -538,31 +576,35 @@ func bodyAllowedForStatus(status int) bool {
return true return true
} }
// Status sets the HTTP response code.
func (c *Context) Status(code int) { func (c *Context) Status(code int) {
c.writermem.WriteHeader(code) c.writermem.WriteHeader(code)
} }
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value) // Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
// It writes a header in the response. // It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)` // If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) { func (c *Context) Header(key, value string) {
if len(value) == 0 { if value == "" {
c.Writer.Header().Del(key) c.Writer.Header().Del(key)
} else { } else {
c.Writer.Header().Set(key, value) c.Writer.Header().Set(key, value)
} }
} }
// GetHeader returns value from request headers // GetHeader returns value from request headers.
func (c *Context) GetHeader(key string) string { func (c *Context) GetHeader(key string) string {
return c.requestHeader(key) return c.requestHeader(key)
} }
// GetRawData return stream data // GetRawData return stream data.
func (c *Context) GetRawData() ([]byte, error) { func (c *Context) GetRawData() ([]byte, error) {
return ioutil.ReadAll(c.Request.Body) 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) { func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
if path == "" { if path == "" {
path = "/" path = "/"
@ -578,6 +620,10 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
}) })
} }
// 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) { func (c *Context) Cookie(name string) (string, error) {
cookie, err := c.Request.Cookie(name) cookie, err := c.Request.Cookie(name)
if err != nil { if err != nil {

View File

@ -45,6 +45,7 @@ func createMultipartRequest() *http.Request {
must(mw.WriteField("id", "")) must(mw.WriteField("id", ""))
must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_local", "31/12/2016 14:55"))
must(mw.WriteField("time_utc", "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"))
req, err := http.NewRequest("POST", "/", body) req, err := http.NewRequest("POST", "/", body)
must(err) must(err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
@ -72,12 +73,18 @@ func TestContextFormFile(t *testing.T) {
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.Equal(t, "test", f.Filename) assert.Equal(t, "test", f.Filename)
} }
assert.NoError(t, c.SaveUploadedFile(f, "test"))
} }
func TestContextMultipartForm(t *testing.T) { func TestContextMultipartForm(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
mw.WriteField("foo", "bar") mw.WriteField("foo", "bar")
w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) {
w.Write([]byte("test"))
}
mw.Close() mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", buf) c.Request, _ = http.NewRequest("POST", "/", buf)
@ -86,6 +93,42 @@ func TestContextMultipartForm(t *testing.T) {
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.NotNil(t, f) 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) { func TestContextReset(t *testing.T) {
@ -137,14 +180,14 @@ func TestContextSetGet(t *testing.T) {
c.Set("foo", "bar") c.Set("foo", "bar")
value, err := c.Get("foo") value, err := c.Get("foo")
assert.Equal(t, value, "bar") assert.Equal(t, "bar", value)
assert.True(t, err) assert.True(t, err)
value, err = c.Get("foo2") value, err = c.Get("foo2")
assert.Nil(t, value) assert.Nil(t, value)
assert.False(t, err) assert.False(t, err)
assert.Equal(t, c.MustGet("foo"), "bar") assert.Equal(t, "bar", c.MustGet("foo"))
assert.Panics(t, func() { c.MustGet("no_exist") }) assert.Panics(t, func() { c.MustGet("no_exist") })
} }
@ -178,7 +221,7 @@ func TestContextGetString(t *testing.T) {
func TestContextSetGetBool(t *testing.T) { func TestContextSetGetBool(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("bool", true) c.Set("bool", true)
assert.Equal(t, true, c.GetBool("bool")) assert.True(t, c.GetBool("bool"))
} }
func TestContextGetInt(t *testing.T) { func TestContextGetInt(t *testing.T) {
@ -295,26 +338,26 @@ func TestContextQuery(t *testing.T) {
value, ok := c.GetQuery("foo") value, ok := c.GetQuery("foo")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "bar") assert.Equal(t, "bar", value)
assert.Equal(t, c.DefaultQuery("foo", "none"), "bar") assert.Equal(t, "bar", c.DefaultQuery("foo", "none"))
assert.Equal(t, c.Query("foo"), "bar") assert.Equal(t, "bar", c.Query("foo"))
value, ok = c.GetQuery("page") value, ok = c.GetQuery("page")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "10") assert.Equal(t, "10", value)
assert.Equal(t, c.DefaultQuery("page", "0"), "10") assert.Equal(t, "10", c.DefaultQuery("page", "0"))
assert.Equal(t, c.Query("page"), "10") assert.Equal(t, "10", c.Query("page"))
value, ok = c.GetQuery("id") value, ok = c.GetQuery("id")
assert.True(t, ok) assert.True(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Equal(t, c.DefaultQuery("id", "nada"), "") assert.Empty(t, c.DefaultQuery("id", "nada"))
assert.Empty(t, c.Query("id")) assert.Empty(t, c.Query("id"))
value, ok = c.GetQuery("NoKey") value, ok = c.GetQuery("NoKey")
assert.False(t, ok) assert.False(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Equal(t, c.DefaultQuery("NoKey", "nada"), "nada") assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada"))
assert.Empty(t, c.Query("NoKey")) assert.Empty(t, c.Query("NoKey"))
// postform should not mess // postform should not mess
@ -330,29 +373,29 @@ func TestContextQueryAndPostForm(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body)
c.Request.Header.Add("Content-Type", MIMEPOSTForm) c.Request.Header.Add("Content-Type", MIMEPOSTForm)
assert.Equal(t, c.DefaultPostForm("foo", "none"), "bar") assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
assert.Equal(t, c.PostForm("foo"), "bar") assert.Equal(t, "bar", c.PostForm("foo"))
assert.Empty(t, c.Query("foo")) assert.Empty(t, c.Query("foo"))
value, ok := c.GetPostForm("page") value, ok := c.GetPostForm("page")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "11") assert.Equal(t, "11", value)
assert.Equal(t, c.DefaultPostForm("page", "0"), "11") assert.Equal(t, "11", c.DefaultPostForm("page", "0"))
assert.Equal(t, c.PostForm("page"), "11") assert.Equal(t, "11", c.PostForm("page"))
assert.Equal(t, c.Query("page"), "") assert.Empty(t, c.Query("page"))
value, ok = c.GetPostForm("both") value, ok = c.GetPostForm("both")
assert.True(t, ok) assert.True(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Empty(t, c.PostForm("both")) assert.Empty(t, c.PostForm("both"))
assert.Equal(t, c.DefaultPostForm("both", "nothing"), "") assert.Empty(t, c.DefaultPostForm("both", "nothing"))
assert.Equal(t, c.Query("both"), "GET") assert.Equal(t, "GET", c.Query("both"), "GET")
value, ok = c.GetQuery("id") value, ok = c.GetQuery("id")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "main") assert.Equal(t, "main", value)
assert.Equal(t, c.DefaultPostForm("id", "000"), "000") assert.Equal(t, "000", c.DefaultPostForm("id", "000"))
assert.Equal(t, c.Query("id"), "main") assert.Equal(t, "main", c.Query("id"))
assert.Empty(t, c.PostForm("id")) assert.Empty(t, c.PostForm("id"))
value, ok = c.GetQuery("NoKey") value, ok = c.GetQuery("NoKey")
@ -361,8 +404,8 @@ func TestContextQueryAndPostForm(t *testing.T) {
value, ok = c.GetPostForm("NoKey") value, ok = c.GetPostForm("NoKey")
assert.False(t, ok) assert.False(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Equal(t, c.DefaultPostForm("NoKey", "nada"), "nada") assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada"))
assert.Equal(t, c.DefaultQuery("NoKey", "nothing"), "nothing") assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing"))
assert.Empty(t, c.PostForm("NoKey")) assert.Empty(t, c.PostForm("NoKey"))
assert.Empty(t, c.Query("NoKey")) assert.Empty(t, c.Query("NoKey"))
@ -374,11 +417,11 @@ func TestContextQueryAndPostForm(t *testing.T) {
Array []string `form:"array[]"` Array []string `form:"array[]"`
} }
assert.NoError(t, c.Bind(&obj)) assert.NoError(t, c.Bind(&obj))
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, "bar", obj.Foo, "bar")
assert.Equal(t, obj.ID, "main") assert.Equal(t, "main", obj.ID, "main")
assert.Equal(t, obj.Page, 11) assert.Equal(t, 11, obj.Page, 11)
assert.Equal(t, obj.Both, "") assert.Empty(t, obj.Both)
assert.Equal(t, obj.Array, []string{"first", "second"}) assert.Equal(t, []string{"first", "second"}, obj.Array)
values, ok := c.GetQueryArray("array[]") values, ok := c.GetQueryArray("array[]")
assert.True(t, ok) assert.True(t, ok)
@ -409,37 +452,41 @@ func TestContextPostFormMultipart(t *testing.T) {
ID string `form:"id"` ID string `form:"id"`
TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` 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"` 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"` BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"`
} }
assert.NoError(t, c.Bind(&obj)) assert.NoError(t, c.Bind(&obj))
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, obj.Bar, "10") assert.Equal(t, "10", obj.Bar)
assert.Equal(t, obj.BarAsInt, 10) assert.Equal(t, 10, obj.BarAsInt)
assert.Equal(t, obj.Array, []string{"first", "second"}) assert.Equal(t, []string{"first", "second"}, obj.Array)
assert.Equal(t, obj.ID, "") assert.Empty(t, obj.ID)
assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55") assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04"))
assert.Equal(t, obj.TimeLocal.Location(), time.Local) assert.Equal(t, time.Local, obj.TimeLocal.Location())
assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55") assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04"))
assert.Equal(t, obj.TimeUTC.Location(), time.UTC) 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()) assert.True(t, obj.BlankTime.IsZero())
value, ok := c.GetQuery("foo") value, ok := c.GetQuery("foo")
assert.False(t, ok) assert.False(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Empty(t, c.Query("bar")) assert.Empty(t, c.Query("bar"))
assert.Equal(t, c.DefaultQuery("id", "nothing"), "nothing") assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing"))
value, ok = c.GetPostForm("foo") value, ok = c.GetPostForm("foo")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "bar") assert.Equal(t, "bar", value)
assert.Equal(t, c.PostForm("foo"), "bar") assert.Equal(t, "bar", c.PostForm("foo"))
value, ok = c.GetPostForm("array") value, ok = c.GetPostForm("array")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, value, "first") assert.Equal(t, "first", value)
assert.Equal(t, c.PostForm("array"), "first") assert.Equal(t, "first", c.PostForm("array"))
assert.Equal(t, c.DefaultPostForm("bar", "nothing"), "10") assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing"))
value, ok = c.GetPostForm("id") value, ok = c.GetPostForm("id")
assert.True(t, ok) assert.True(t, ok)
@ -450,7 +497,7 @@ func TestContextPostFormMultipart(t *testing.T) {
value, ok = c.GetPostForm("nokey") value, ok = c.GetPostForm("nokey")
assert.False(t, ok) assert.False(t, ok)
assert.Empty(t, value) assert.Empty(t, value)
assert.Equal(t, c.DefaultPostForm("nokey", "nothing"), "nothing") assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing"))
values, ok := c.GetPostFormArray("array") values, ok := c.GetPostFormArray("array")
assert.True(t, ok) assert.True(t, ok)
@ -472,13 +519,13 @@ func TestContextPostFormMultipart(t *testing.T) {
func TestContextSetCookie(t *testing.T) { func TestContextSetCookie(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.SetCookie("user", "gin", 1, "/", "localhost", true, true) c.SetCookie("user", "gin", 1, "/", "localhost", true, true)
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
} }
func TestContextSetCookiePathEmpty(t *testing.T) { func TestContextSetCookiePathEmpty(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.SetCookie("user", "gin", 1, "", "localhost", true, true) c.SetCookie("user", "gin", 1, "", "localhost", true, true)
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
} }
func TestContextGetCookie(t *testing.T) { func TestContextGetCookie(t *testing.T) {
@ -486,17 +533,17 @@ func TestContextGetCookie(t *testing.T) {
c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin") c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user") cookie, _ := c.Cookie("user")
assert.Equal(t, cookie, "gin") assert.Equal(t, "gin", cookie)
_, err := c.Cookie("nokey") _, err := c.Cookie("nokey")
assert.Error(t, err) assert.Error(t, err)
} }
func TestContextBodyAllowedForStatus(t *testing.T) { func TestContextBodyAllowedForStatus(t *testing.T) {
assert.Equal(t, false, bodyAllowedForStatus(102)) assert.False(t, false, bodyAllowedForStatus(102))
assert.Equal(t, false, bodyAllowedForStatus(204)) assert.False(t, false, bodyAllowedForStatus(204))
assert.Equal(t, false, bodyAllowedForStatus(304)) assert.False(t, false, bodyAllowedForStatus(304))
assert.Equal(t, true, bodyAllowedForStatus(500)) assert.True(t, true, bodyAllowedForStatus(500))
} }
type TestPanicRender struct { type TestPanicRender struct {
@ -542,7 +589,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
c.JSON(204, H{"foo": "bar"}) c.JSON(204, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -569,7 +616,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
c.JSON(204, H{"foo": "bar"}) c.JSON(204, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json")
} }
@ -581,7 +628,7 @@ func TestContextRenderIndentedJSON(t *testing.T) {
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) 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")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -594,7 +641,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -607,9 +654,9 @@ func TestContextRenderSecureJSON(t *testing.T) {
router.SecureJsonPrefix("&&&START&&&") router.SecureJsonPrefix("&&&START&&&")
c.SecureJSON(201, []string{"foo", "bar"}) c.SecureJSON(201, []string{"foo", "bar"})
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]") assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// Tests that no Custom JSON is rendered if code is 204 // Tests that no Custom JSON is rendered if code is 204
@ -620,8 +667,8 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
c.SecureJSON(204, []string{"foo", "bar"}) c.SecureJSON(204, []string{"foo", "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// Tests that the response executes the templates // Tests that the response executes the templates
@ -629,14 +676,39 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
func TestContextRenderHTML(t *testing.T) { func TestContextRenderHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.HTML(201, "t", H{"name": "alexandernyquist"}) c.HTML(201, "t", H{"name": "alexandernyquist"})
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "Hello alexandernyquist") assert.Equal(t, "Hello alexandernyquist", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") 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(201, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 201, 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 // Tests that no HTML is rendered if code is 204
@ -649,8 +721,8 @@ func TestContextRenderNoContentHTML(t *testing.T) {
c.HTML(204, "t", H{"name": "alexandernyquist"}) c.HTML(204, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestContextXML tests that the response is serialized as XML // TestContextXML tests that the response is serialized as XML
@ -661,9 +733,9 @@ func TestContextRenderXML(t *testing.T) {
c.XML(201, H{"foo": "bar"}) c.XML(201, H{"foo": "bar"})
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>") assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// Tests that no XML is rendered if code is 204 // Tests that no XML is rendered if code is 204
@ -674,8 +746,8 @@ func TestContextRenderNoContentXML(t *testing.T) {
c.XML(204, H{"foo": "bar"}) c.XML(204, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestContextString tests that the response is returned // TestContextString tests that the response is returned
@ -686,9 +758,9 @@ func TestContextRenderString(t *testing.T) {
c.String(201, "test %s %d", "string", 2) c.String(201, "test %s %d", "string", 2)
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "test string 2") assert.Equal(t, "test string 2", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// Tests that no String is rendered if code is 204 // Tests that no String is rendered if code is 204
@ -699,8 +771,8 @@ func TestContextRenderNoContentString(t *testing.T) {
c.String(204, "test %s %d", "string", 2) c.String(204, "test %s %d", "string", 2)
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestContextString tests that the response is returned // TestContextString tests that the response is returned
@ -712,9 +784,9 @@ func TestContextRenderHTMLString(t *testing.T) {
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.String(201, "<html>%s %d</html>", "string", 3) c.String(201, "<html>%s %d</html>", "string", 3)
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "<html>string 3</html>") assert.Equal(t, "<html>string 3</html>", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// Tests that no HTML String is rendered if code is 204 // Tests that no HTML String is rendered if code is 204
@ -726,8 +798,8 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
c.String(204, "<html>%s %d</html>", "string", 3) c.String(204, "<html>%s %d</html>", "string", 3)
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestContextData tests that the response can be written from `bytesting` // TestContextData tests that the response can be written from `bytesting`
@ -738,9 +810,9 @@ func TestContextRenderData(t *testing.T) {
c.Data(201, "text/csv", []byte(`foo,bar`)) c.Data(201, "text/csv", []byte(`foo,bar`))
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "foo,bar") assert.Equal(t, "foo,bar", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
} }
// Tests that no Custom Data is rendered if code is 204 // Tests that no Custom Data is rendered if code is 204
@ -751,8 +823,8 @@ func TestContextRenderNoContentData(t *testing.T) {
c.Data(204, "text/csv", []byte(`foo,bar`)) c.Data(204, "text/csv", []byte(`foo,bar`))
assert.Equal(t, 204, w.Code) assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
} }
func TestContextRenderSSE(t *testing.T) { func TestContextRenderSSE(t *testing.T) {
@ -779,9 +851,9 @@ func TestContextRenderFile(t *testing.T) {
c.Request, _ = http.NewRequest("GET", "/", nil) c.Request, _ = http.NewRequest("GET", "/", nil)
c.File("./gin.go") c.File("./gin.go")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestContextRenderYAML tests that the response is serialized as YAML // TestContextRenderYAML tests that the response is serialized as YAML
@ -792,9 +864,9 @@ func TestContextRenderYAML(t *testing.T) {
c.YAML(201, H{"foo": "bar"}) c.YAML(201, H{"foo": "bar"})
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Body.String(), "foo: bar\n") assert.Equal(t, "foo: bar\n", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8") assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
func TestContextHeaders(t *testing.T) { func TestContextHeaders(t *testing.T) {
@ -802,13 +874,13 @@ func TestContextHeaders(t *testing.T) {
c.Header("Content-Type", "text/plain") c.Header("Content-Type", "text/plain")
c.Header("X-Custom", "value") c.Header("X-Custom", "value")
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/plain") assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type"))
assert.Equal(t, c.Writer.Header().Get("X-Custom"), "value") assert.Equal(t, "value", c.Writer.Header().Get("X-Custom"))
c.Header("Content-Type", "text/html") c.Header("Content-Type", "text/html")
c.Header("X-Custom", "") c.Header("X-Custom", "")
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/html") assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type"))
_, exist := c.Writer.Header()["X-Custom"] _, exist := c.Writer.Header()["X-Custom"]
assert.False(t, exist) assert.False(t, exist)
} }
@ -824,8 +896,8 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
c.Redirect(301, "/path") c.Redirect(301, "/path")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 301) assert.Equal(t, 301, w.Code)
assert.Equal(t, w.Header().Get("Location"), "/path") assert.Equal(t, "/path", w.Header().Get("Location"))
} }
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
@ -836,8 +908,8 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
c.Redirect(302, "http://google.com") c.Redirect(302, "http://google.com")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 302) assert.Equal(t, 302, w.Code)
assert.Equal(t, w.Header().Get("Location"), "http://google.com") assert.Equal(t, "http://google.com", w.Header().Get("Location"))
} }
func TestContextRenderRedirectWith201(t *testing.T) { func TestContextRenderRedirectWith201(t *testing.T) {
@ -848,8 +920,8 @@ func TestContextRenderRedirectWith201(t *testing.T) {
c.Redirect(201, "/resource") c.Redirect(201, "/resource")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 201) assert.Equal(t, 201, w.Code)
assert.Equal(t, w.Header().Get("Location"), "/resource") assert.Equal(t, "/resource", w.Header().Get("Location"))
} }
func TestContextRenderRedirectAll(t *testing.T) { func TestContextRenderRedirectAll(t *testing.T) {
@ -930,8 +1002,8 @@ func TestContextNegotiationFormat(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
assert.Panics(t, func() { c.NegotiateFormat() }) assert.Panics(t, func() { c.NegotiateFormat() })
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON))
} }
func TestContextNegotiationFormatWithAccept(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) {
@ -939,9 +1011,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", nil) 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.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML) assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML))
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML))
assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") assert.Empty(t, c.NegotiateFormat(MIMEJSON))
} }
func TestContextNegotiationFormatCustum(t *testing.T) { func TestContextNegotiationFormatCustum(t *testing.T) {
@ -952,9 +1024,9 @@ func TestContextNegotiationFormatCustum(t *testing.T) {
c.Accepted = nil c.Accepted = nil
c.SetAccepted(MIMEJSON, MIMEXML) c.SetAccepted(MIMEJSON, MIMEXML)
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEXML) assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML))
assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON))
} }
func TestContextIsAborted(t *testing.T) { func TestContextIsAborted(t *testing.T) {
@ -980,9 +1052,9 @@ func TestContextAbortWithStatus(t *testing.T) {
c.index = 4 c.index = 4
c.AbortWithStatus(401) c.AbortWithStatus(401)
assert.Equal(t, c.index, abortIndex) assert.Equal(t, abortIndex, c.index)
assert.Equal(t, c.Writer.Status(), 401) assert.Equal(t, 401, c.Writer.Status())
assert.Equal(t, w.Code, 401) assert.Equal(t, 401, w.Code)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1002,13 +1074,13 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
c.AbortWithStatusJSON(415, in) c.AbortWithStatusJSON(415, in)
assert.Equal(t, c.index, abortIndex) assert.Equal(t, abortIndex, c.index)
assert.Equal(t, c.Writer.Status(), 415) assert.Equal(t, 415, c.Writer.Status())
assert.Equal(t, w.Code, 415) assert.Equal(t, 415, w.Code)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
contentType := w.Header().Get("Content-Type") contentType := w.Header().Get("Content-Type")
assert.Equal(t, contentType, "application/json; charset=utf-8") assert.Equal(t, "application/json; charset=utf-8", contentType)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(w.Body) buf.ReadFrom(w.Body)
@ -1022,7 +1094,7 @@ func TestContextError(t *testing.T) {
c.Error(errors.New("first error")) c.Error(errors.New("first error"))
assert.Len(t, c.Errors, 1) assert.Len(t, c.Errors, 1)
assert.Equal(t, c.Errors.String(), "Error #01: first error\n") assert.Equal(t, "Error #01: first error\n", c.Errors.String())
c.Error(&Error{ c.Error(&Error{
Err: errors.New("second error"), Err: errors.New("second error"),
@ -1031,13 +1103,13 @@ func TestContextError(t *testing.T) {
}) })
assert.Len(t, c.Errors, 2) assert.Len(t, c.Errors, 2)
assert.Equal(t, c.Errors[0].Err, errors.New("first error")) assert.Equal(t, errors.New("first error"), c.Errors[0].Err)
assert.Nil(t, c.Errors[0].Meta) assert.Nil(t, c.Errors[0].Meta)
assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate) assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type)
assert.Equal(t, c.Errors[1].Err, errors.New("second error")) assert.Equal(t, errors.New("second error"), c.Errors[1].Err)
assert.Equal(t, c.Errors[1].Meta, "some data 2") assert.Equal(t, "some data 2", c.Errors[1].Meta)
assert.Equal(t, c.Errors[1].Type, ErrorTypePublic) assert.Equal(t, ErrorTypePublic, c.Errors[1].Type)
assert.Equal(t, c.Errors.Last(), c.Errors[1]) assert.Equal(t, c.Errors.Last(), c.Errors[1])
@ -1055,12 +1127,12 @@ func TestContextTypedError(t *testing.T) {
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
for _, err := range c.Errors.ByType(ErrorTypePublic) { for _, err := range c.Errors.ByType(ErrorTypePublic) {
assert.Equal(t, err.Type, ErrorTypePublic) assert.Equal(t, ErrorTypePublic, err.Type)
} }
for _, err := range c.Errors.ByType(ErrorTypePrivate) { for _, err := range c.Errors.ByType(ErrorTypePrivate) {
assert.Equal(t, err.Type, ErrorTypePrivate) assert.Equal(t, ErrorTypePrivate, err.Type)
} }
assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"}) assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors())
} }
func TestContextAbortWithError(t *testing.T) { func TestContextAbortWithError(t *testing.T) {
@ -1069,8 +1141,8 @@ func TestContextAbortWithError(t *testing.T) {
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
assert.Equal(t, w.Code, 401) assert.Equal(t, 401, w.Code)
assert.Equal(t, c.index, abortIndex) assert.Equal(t, abortIndex, c.index)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1101,7 +1173,7 @@ func TestContextClientIP(t *testing.T) {
// no port // no port
c.Request.RemoteAddr = "50.50.50.50" c.Request.RemoteAddr = "50.50.50.50"
assert.Equal(t, "", c.ClientIP()) assert.Empty(t, c.ClientIP())
} }
func TestContextContentType(t *testing.T) { func TestContextContentType(t *testing.T) {
@ -1109,7 +1181,7 @@ func TestContextContentType(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") c.Request.Header.Set("Content-Type", "application/json; charset=utf-8")
assert.Equal(t, c.ContentType(), "application/json") assert.Equal(t, "application/json", c.ContentType())
} }
func TestContextAutoBindJSON(t *testing.T) { func TestContextAutoBindJSON(t *testing.T) {
@ -1122,8 +1194,8 @@ func TestContextAutoBindJSON(t *testing.T) {
Bar string `json:"bar"` Bar string `json:"bar"`
} }
assert.NoError(t, c.Bind(&obj)) assert.NoError(t, c.Bind(&obj))
assert.Equal(t, obj.Bar, "foo") assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, "bar", obj.Foo)
assert.Empty(t, c.Errors) assert.Empty(t, c.Errors)
} }
@ -1139,9 +1211,25 @@ func TestContextBindWithJSON(t *testing.T) {
Bar string `json:"bar"` Bar string `json:"bar"`
} }
assert.NoError(t, c.BindJSON(&obj)) assert.NoError(t, c.BindJSON(&obj))
assert.Equal(t, obj.Bar, "foo") assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, obj.Foo, "bar") assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, w.Body.Len(), 0) 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) { func TestContextBadAutoBind(t *testing.T) {
@ -1161,10 +1249,77 @@ func TestContextBadAutoBind(t *testing.T) {
assert.Empty(t, obj.Bar) assert.Empty(t, obj.Bar)
assert.Empty(t, obj.Foo) assert.Empty(t, obj.Foo)
assert.Equal(t, w.Code, 400) assert.Equal(t, 400, w.Code)
assert.True(t, c.IsAborted()) 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 TestContextGolangContext(t *testing.T) { func TestContextGolangContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
@ -1177,7 +1332,7 @@ func TestContextGolangContext(t *testing.T) {
assert.Nil(t, c.Value("foo")) assert.Nil(t, c.Value("foo"))
c.Set("foo", "bar") c.Set("foo", "bar")
assert.Equal(t, c.Value("foo"), "bar") assert.Equal(t, "bar", c.Value("foo"))
assert.Nil(t, c.Value(1)) assert.Nil(t, c.Value(1))
} }
@ -1209,7 +1364,7 @@ func TestGetRequestHeaderValue(t *testing.T) {
c.Request.Header.Set("Gin-Version", "1.0.0") c.Request.Header.Set("Gin-Version", "1.0.0")
assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version"))
assert.Equal(t, "", c.GetHeader("Connection")) assert.Empty(t, c.GetHeader("Connection"))
} }
func TestContextGetRawData(t *testing.T) { func TestContextGetRawData(t *testing.T) {

13
coverage.sh Normal file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
echo "mode: count" > coverage.out
for d in $(go list ./... | grep -E 'gin$|binding$|render$'); 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

View File

@ -15,7 +15,7 @@ func init() {
} }
// IsDebugging returns true if the framework is running in debug mode. // IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.Release) to disable debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode.
func IsDebugging() bool { func IsDebugging() bool {
return ginMode == debugCode return ginMode == debugCode
} }
@ -46,6 +46,12 @@ func debugPrint(format string, values ...interface{}) {
} }
} }
func debugPrintWARNINGDefault() {
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)
}
func debugPrintWARNINGNew() { func debugPrintWARNINGNew() {
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production. debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release - using env: export GIN_MODE=release

View File

@ -42,7 +42,7 @@ func TestDebugPrint(t *testing.T) {
SetMode(DebugMode) SetMode(DebugMode)
debugPrint("these are %d %s\n", 2, "error messages") debugPrint("these are %d %s\n", 2, "error messages")
assert.Equal(t, w.String(), "[GIN-debug] these are 2 error messages\n") assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String())
} }
func TestDebugPrintError(t *testing.T) { func TestDebugPrintError(t *testing.T) {
@ -55,7 +55,7 @@ func TestDebugPrintError(t *testing.T) {
assert.Empty(t, w.String()) assert.Empty(t, w.String())
debugPrintError(errors.New("this is an error")) debugPrintError(errors.New("this is an error"))
assert.Equal(t, w.String(), "[GIN-debug] [ERROR] this is an error\n") assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String())
} }
func TestDebugPrintRoutes(t *testing.T) { func TestDebugPrintRoutes(t *testing.T) {
@ -83,7 +83,25 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
defer teardown() defer teardown()
debugPrintWARNINGSetHTMLTemplate() debugPrintWARNINGSetHTMLTemplate()
assert.Equal(t, w.String(), "[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") 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] 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) { func setup(w io.Writer) {

View File

@ -15,7 +15,7 @@ import (
func (c *Context) BindWith(obj interface{}, b binding.Binding) error { func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
be deprecated, please check issue #662 and either use MustBindWith() if you be deprecated, please check issue #662 and either use MustBindWith() if you
want HTTP 400 to be automatically returned if any error occur, of use want HTTP 400 to be automatically returned if any error occur, or use
ShouldBindWith() if you need to manage the error.`) ShouldBindWith() if you need to manage the error.`)
return c.MustBindWith(obj, b) return c.MustBindWith(obj, b)
} }

31
deprecated_test.go Normal file
View File

@ -0,0 +1,31 @@
// 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())
}

View File

@ -9,11 +9,9 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/json-iterator/go" "github.com/gin-gonic/gin/json"
) )
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type ErrorType uint64 type ErrorType uint64
const ( const (
@ -67,7 +65,7 @@ func (msg *Error) JSON() interface{} {
return json return json
} }
// MarshalJSON implements the json.Marshaller interface // MarshalJSON implements the json.Marshaller interface.
func (msg *Error) MarshalJSON() ([]byte, error) { func (msg *Error) MarshalJSON() ([]byte, error) {
return json.Marshal(msg.JSON()) return json.Marshal(msg.JSON())
} }
@ -82,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool {
} }
// ByType returns a readonly copy filtered the byte. // ByType returns a readonly copy filtered the byte.
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
func (a errorMsgs) ByType(typ ErrorType) errorMsgs { func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
if len(a) == 0 { if len(a) == 0 {
return nil return nil
@ -100,7 +98,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
} }
// Last returns the last error in the slice. It returns nil if the array is empty. // Last returns the last error in the slice. It returns nil if the array is empty.
// Shortcut for errors[len(errors)-1] // Shortcut for errors[len(errors)-1].
func (a errorMsgs) Last() *Error { func (a errorMsgs) Last() *Error {
if length := len(a); length > 0 { if length := len(a); length > 0 {
return a[length-1] return a[length-1]
@ -150,7 +148,7 @@ func (a errorMsgs) String() string {
} }
var buffer bytes.Buffer var buffer bytes.Buffer
for i, msg := range a { for i, msg := range a {
fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err) fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
if msg.Meta != nil { if msg.Meta != nil {
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta) fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
} }

View File

@ -8,6 +8,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/gin-gonic/gin/json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -59,7 +60,7 @@ func TestError(t *testing.T) {
data string data string
} }
err.SetMeta(customError{status: "200", data: "other data"}) err.SetMeta(customError{status: "200", data: "other data"})
assert.Equal(t, err.JSON(), customError{status: "200", data: "other data"}) assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON())
} }
func TestErrorSlice(t *testing.T) { func TestErrorSlice(t *testing.T) {
@ -70,33 +71,33 @@ func TestErrorSlice(t *testing.T) {
} }
assert.Equal(t, errs, errs.ByType(ErrorTypeAny)) assert.Equal(t, errs, errs.ByType(ErrorTypeAny))
assert.Equal(t, errs.Last().Error(), "third") assert.Equal(t, "third", errs.Last().Error())
assert.Equal(t, errs.Errors(), []string{"first", "second", "third"}) assert.Equal(t, []string{"first", "second", "third"}, errs.Errors())
assert.Equal(t, errs.ByType(ErrorTypePublic).Errors(), []string{"third"}) assert.Equal(t, []string{"third"}, errs.ByType(ErrorTypePublic).Errors())
assert.Equal(t, errs.ByType(ErrorTypePrivate).Errors(), []string{"first", "second"}) assert.Equal(t, []string{"first", "second"}, errs.ByType(ErrorTypePrivate).Errors())
assert.Equal(t, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors(), []string{"first", "second", "third"}) assert.Equal(t, []string{"first", "second", "third"}, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors())
assert.Empty(t, errs.ByType(ErrorTypeBind)) assert.Empty(t, errs.ByType(ErrorTypeBind))
assert.Empty(t, errs.ByType(ErrorTypeBind).String()) assert.Empty(t, errs.ByType(ErrorTypeBind).String())
assert.Equal(t, errs.String(), `Error #01: first assert.Equal(t, `Error #01: first
Error #02: second Error #02: second
Meta: some data Meta: some data
Error #03: third Error #03: third
Meta: map[status:400] Meta: map[status:400]
`) `, errs.String())
assert.Equal(t, errs.JSON(), []interface{}{ assert.Equal(t, []interface{}{
H{"error": "first"}, H{"error": "first"},
H{"error": "second", "meta": "some data"}, H{"error": "second", "meta": "some data"},
H{"error": "third", "status": "400"}, H{"error": "third", "status": "400"},
}) }, errs.JSON())
jsonBytes, _ := json.Marshal(errs) jsonBytes, _ := json.Marshal(errs)
assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes))
errs = errorMsgs{ errs = errorMsgs{
{Err: errors.New("first"), Type: ErrorTypePrivate}, {Err: errors.New("first"), Type: ErrorTypePrivate},
} }
assert.Equal(t, errs.JSON(), H{"error": "first"}) assert.Equal(t, H{"error": "first"}, errs.JSON())
jsonBytes, _ = json.Marshal(errs) jsonBytes, _ = json.Marshal(errs)
assert.Equal(t, string(jsonBytes), "{\"error\":\"first\"}") assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes))
errs = errorMsgs{} errs = errorMsgs{}
assert.Nil(t, errs.Last()) assert.Nil(t, errs.Last())

View File

@ -1,7 +1,8 @@
# Guide to run Gin under App Engine LOCAL Development Server # Guide to run Gin under App Engine LOCAL Development Server
1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.)
2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go` 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` 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/` 4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/`
5. Run it: `$ goapp serve app-engine/` 5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2)

View File

@ -13,10 +13,10 @@ func init() {
// Define your handlers // Define your handlers
r.GET("/", func(c *gin.Context) { r.GET("/", func(c *gin.Context) {
c.String(200, "Hello World!") c.String(http.StatusOK, "Hello World!")
}) })
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
// Handle all requests using net/http // Handle all requests using net/http

View File

@ -6,7 +6,7 @@ import (
var DB = make(map[string]string) var DB = make(map[string]string)
func main() { func setupRouter() *gin.Engine {
// Disable Console Color // Disable Console Color
// gin.DisableConsoleColor() // gin.DisableConsoleColor()
r := gin.Default() r := gin.Default()
@ -53,6 +53,11 @@ func main() {
} }
}) })
return r
}
func main() {
r := setupRouter()
// Listen and Server in 0.0.0.0:8080 // Listen and Server in 0.0.0.0:8080
r.Run(":8080") r.Run(":8080")
} }

View File

@ -0,0 +1,20 @@
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())
}

View File

@ -0,0 +1,49 @@
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()})
}
}

View File

@ -41,5 +41,5 @@ func main() {
} }
} }
log.Println("Server exist") log.Println("Server exiting")
} }

View File

@ -27,8 +27,8 @@ func main() {
go func() { go func() {
// service connections // service connections
if err := srv.ListenAndServe(); err != nil { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("listen: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
}() }()
@ -44,5 +44,5 @@ func main() {
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err) log.Fatal("Server Shutdown:", err)
} }
log.Println("Server exist") log.Println("Server exiting")
} }

View File

@ -0,0 +1,74 @@
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)
}
}

View File

@ -29,8 +29,8 @@ func statsWorker() {
"timestamp": uint64(time.Now().Unix()), "timestamp": uint64(time.Now().Unix()),
"HeapInuse": stats.HeapInuse, "HeapInuse": stats.HeapInuse,
"StackInuse": stats.StackInuse, "StackInuse": stats.StackInuse,
"Mallocs": (stats.Mallocs - lastMallocs), "Mallocs": stats.Mallocs - lastMallocs,
"Frees": (stats.Frees - lastFrees), "Frees": stats.Frees - lastFrees,
"Inbound": uint64(messages.Get("inbound")), "Inbound": uint64(messages.Get("inbound")),
"Outbound": uint64(messages.Get("outbound")), "Outbound": uint64(messages.Get("outbound")),
"Connected": connectedUsers(), "Connected": connectedUsers(),

View File

@ -0,0 +1,50 @@
## 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

View File

@ -0,0 +1,64 @@
package 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(),
})
}
}

32
examples/template/main.go Normal file
View File

@ -0,0 +1,32 @@
package 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("../../fixtures/basic/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")
}

View File

@ -2,15 +2,15 @@ package main
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func main() { func main() {
router := gin.Default() 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.Static("/", "./public")
router.POST("/upload", func(c *gin.Context) { router.POST("/upload", func(c *gin.Context) {
name := c.PostForm("name") name := c.PostForm("name")
@ -25,24 +25,10 @@ func main() {
files := form.File["files"] files := form.File["files"]
for _, file := range files { for _, file := range files {
// Source if err := c.SaveUploadedFile(file, file.Filename); err != nil {
src, err := file.Open() c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error()))
return return
} }
defer src.Close()
// Destination
dst, err := os.Create(file.Filename)
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error()))
return
}
defer dst.Close()
// Copy
io.Copy(dst, src)
} }
c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email))

View File

@ -2,15 +2,15 @@ package main
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func main() { func main() {
router := gin.Default() 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.Static("/", "./public")
router.POST("/upload", func(c *gin.Context) { router.POST("/upload", func(c *gin.Context) {
name := c.PostForm("name") name := c.PostForm("name")
@ -22,23 +22,11 @@ func main() {
c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
return return
} }
src, err := file.Open()
if err != nil { if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
return return
} }
defer src.Close()
// Destination
dst, err := os.Create(file.Filename)
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error()))
return
}
defer dst.Close()
// Copy
io.Copy(dst, src)
c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email)) c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email))
}) })

18
fixtures/testdata/cert.pem vendored Normal file
View File

@ -0,0 +1,18 @@
-----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-----

27
fixtures/testdata/key.pem vendored Normal file
View File

@ -0,0 +1,27 @@
-----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-----

4
fs.go
View File

@ -29,7 +29,7 @@ func Dir(root string, listDirectory bool) http.FileSystem {
return &onlyfilesFS{fs} return &onlyfilesFS{fs}
} }
// Open conforms to http.Filesystem // Open conforms to http.Filesystem.
func (fs onlyfilesFS) Open(name string) (http.File, error) { func (fs onlyfilesFS) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name) f, err := fs.fs.Open(name)
if err != nil { if err != nil {
@ -38,7 +38,7 @@ func (fs onlyfilesFS) Open(name string) (http.File, error) {
return neuteredReaddirFile{f}, nil return neuteredReaddirFile{f}, nil
} }
// Readdir overrides the http.File default implementation // Readdir overrides the http.File default implementation.
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
// this disables directory listing // this disables directory listing
return nil, nil return nil, nil

120
gin.go
View File

@ -14,12 +14,17 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Version is Framework's version const (
const Version = "v1.2" // Version is Framework's version.
Version = "v1.2"
defaultMultipartMemory = 32 << 20 // 32 MB
)
var default404Body = []byte("404 page not found") var (
var default405Body = []byte("405 method not allowed") default404Body = []byte("404 page not found")
var defaultAppEngine bool default405Body = []byte("405 method not allowed")
defaultAppEngine bool
)
type HandlerFunc func(*Context) type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc type HandlersChain []HandlerFunc
@ -44,16 +49,6 @@ type RoutesInfo []RouteInfo
// Create an instance of Engine, by using New() or Default() // Create an instance of Engine, by using New() or Default()
type Engine struct { type Engine struct {
RouterGroup RouterGroup
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees
// Enables automatic redirection if the current route can't be matched but a // Enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists. // handler for the path with (without) the trailing slash exists.
@ -88,10 +83,26 @@ type Engine struct {
// If enabled, the url.RawPath will be used to find parameters. // If enabled, the url.RawPath will be used to find parameters.
UseRawPath bool UseRawPath bool
// If true, the path value will be unescaped. // If true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true, // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped. // as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool 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{} var _ IRouter = &Engine{}
@ -120,8 +131,9 @@ func New() *Engine {
AppEngine: defaultAppEngine, AppEngine: defaultAppEngine,
UseRawPath: false, UseRawPath: false,
UnescapePathValues: true, UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9), trees: make(methodTrees, 0, 9),
delims: render.Delims{"{{", "}}"}, delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);", secureJsonPrefix: "while(1);",
} }
engine.RouterGroup.engine = engine engine.RouterGroup.engine = engine
@ -133,6 +145,7 @@ func New() *Engine {
// Default returns an Engine instance with the Logger and Recovery middleware already attached. // Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine { func Default() *Engine {
debugPrintWARNINGDefault()
engine := New() engine := New()
engine.Use(Logger(), Recovery()) engine.Use(Logger(), Recovery())
return engine return engine
@ -143,34 +156,45 @@ func (engine *Engine) allocateContext() *Context {
} }
func (engine *Engine) Delims(left, right string) *Engine { func (engine *Engine) Delims(left, right string) *Engine {
engine.delims = render.Delims{left, right} engine.delims = render.Delims{Left: left, Right: right}
return engine return engine
} }
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
engine.secureJsonPrefix = prefix engine.secureJsonPrefix = prefix
return engine return engine
} }
// LoadHTMLGlob loads HTML files identified by glob pattern
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLGlob(pattern string) { func (engine *Engine) LoadHTMLGlob(pattern string) {
left := engine.delims.Left
right := engine.delims.Right
if IsDebugging() { if IsDebugging() {
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)))
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
} else { return
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))
engine.SetHTMLTemplate(templ)
}
} }
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
engine.SetHTMLTemplate(templ)
}
// LoadHTMLFiles loads a slice of HTML files
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFiles(files ...string) { func (engine *Engine) LoadHTMLFiles(files ...string) {
if IsDebugging() { if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
} else { return
}
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
engine.SetHTMLTemplate(templ) engine.SetHTMLTemplate(templ)
} }
}
// SetHTMLTemplate associate a template with HTML renderer.
func (engine *Engine) SetHTMLTemplate(templ *template.Template) { func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 { if len(engine.trees) > 0 {
debugPrintWARNINGSetHTMLTemplate() debugPrintWARNINGSetHTMLTemplate()
@ -179,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
} }
// SetFuncMap sets the FuncMap used for template.FuncMap.
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.FuncMap = funcMap engine.FuncMap = funcMap
} }
@ -189,7 +214,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.rebuild404Handlers() engine.rebuild404Handlers()
} }
// NoMethod sets the handlers called when... TODO // NoMethod sets the handlers called when... TODO.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) { func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers engine.noMethod = handlers
engine.rebuild405Handlers() engine.rebuild405Handlers()
@ -215,7 +240,7 @@ func (engine *Engine) rebuild405Handlers() {
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'") assert1(path[0] == '/', "path must begin with '/'")
assert1(len(method) > 0, "HTTP method can not be empty") assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler") assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers) debugPrintRoute(method, path, handlers)
@ -266,7 +291,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // 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) // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) { func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
debugPrint("Listening and serving HTTPS on %s\n", addr) debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() { debugPrintError(err) }() defer func() { debugPrintError(err) }()
@ -312,16 +337,13 @@ func (engine *Engine) HandleContext(c *Context) {
engine.pool.Put(c) engine.pool.Put(c)
} }
func (engine *Engine) handleHTTPRequest(context *Context) { func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := context.Request.Method httpMethod := c.Request.Method
var path string path := c.Request.URL.Path
var unescape bool unescape := false
if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
path = context.Request.URL.RawPath path = c.Request.URL.RawPath
unescape = engine.UnescapePathValues unescape = engine.UnescapePathValues
} else {
path = context.Request.URL.Path
unescape = false
} }
// Find root of the tree for the given HTTP method // Find root of the tree for the given HTTP method
@ -330,20 +352,20 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
if t[i].method == httpMethod { if t[i].method == httpMethod {
root := t[i].root root := t[i].root
// Find route in tree // Find route in tree
handlers, params, tsr := root.getValue(path, context.Params, unescape) handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil { if handlers != nil {
context.handlers = handlers c.handlers = handlers
context.Params = params c.Params = params
context.Next() c.Next()
context.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
return return
} }
if httpMethod != "CONNECT" && path != "/" { if httpMethod != "CONNECT" && path != "/" {
if tsr && engine.RedirectTrailingSlash { if tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(context) redirectTrailingSlash(c)
return return
} }
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) { if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return return
} }
} }
@ -355,15 +377,15 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
for _, tree := range engine.trees { for _, tree := range engine.trees {
if tree.method != httpMethod { if tree.method != httpMethod {
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
context.handlers = engine.allNoMethod c.handlers = engine.allNoMethod
serveError(context, 405, default405Body) serveError(c, 405, default405Body)
return return
} }
} }
} }
} }
context.handlers = engine.allNoRoute c.handlers = engine.allNoRoute
serveError(context, 404, default404Body) serveError(c, 404, default404Body)
} }
var mimePlain = []string{MIMEPlain} var mimePlain = []string{MIMEPlain}
@ -389,8 +411,8 @@ func redirectTrailingSlash(c *Context) {
code = 307 code = 307
} }
if len(path) > 1 && path[len(path)-1] == '/' { if length := len(path); length > 1 && path[length-1] == '/' {
req.URL.Path = path[:len(path)-1] req.URL.Path = path[:length-1]
} else { } else {
req.URL.Path = path + "/" req.URL.Path = path + "/"
} }

View File

@ -9,15 +9,15 @@ import (
"net/http" "net/http"
"sync" "sync"
. "github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
var once sync.Once var once sync.Once
var internalEngine *Engine var internalEngine *gin.Engine
func engine() *Engine { func engine() *gin.Engine {
once.Do(func() { once.Do(func() {
internalEngine = Default() internalEngine = gin.Default()
}) })
return internalEngine return internalEngine
} }
@ -35,65 +35,65 @@ func SetHTMLTemplate(templ *template.Template) {
} }
// NoRoute adds handlers for NoRoute. It return a 404 code by default. // NoRoute adds handlers for NoRoute. It return a 404 code by default.
func NoRoute(handlers ...HandlerFunc) { func NoRoute(handlers ...gin.HandlerFunc) {
engine().NoRoute(handlers...) engine().NoRoute(handlers...)
} }
// NoMethod sets the handlers called when... TODO // NoMethod sets the handlers called when... TODO
func NoMethod(handlers ...HandlerFunc) { func NoMethod(handlers ...gin.HandlerFunc) {
engine().NoMethod(handlers...) engine().NoMethod(handlers...)
} }
// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // 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. // For example, all the routes that use a common middlware for authorization could be grouped.
func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
return engine().Group(relativePath, handlers...) return engine().Group(relativePath, handlers...)
} }
func Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().Handle(httpMethod, relativePath, handlers...) return engine().Handle(httpMethod, relativePath, handlers...)
} }
// POST is a shortcut for router.Handle("POST", path, handle) // POST is a shortcut for router.Handle("POST", path, handle)
func POST(relativePath string, handlers ...HandlerFunc) IRoutes { func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().POST(relativePath, handlers...) return engine().POST(relativePath, handlers...)
} }
// GET is a shortcut for router.Handle("GET", path, handle) // GET is a shortcut for router.Handle("GET", path, handle)
func GET(relativePath string, handlers ...HandlerFunc) IRoutes { func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().GET(relativePath, handlers...) return engine().GET(relativePath, handlers...)
} }
// DELETE is a shortcut for router.Handle("DELETE", path, handle) // DELETE is a shortcut for router.Handle("DELETE", path, handle)
func DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().DELETE(relativePath, handlers...) return engine().DELETE(relativePath, handlers...)
} }
// PATCH is a shortcut for router.Handle("PATCH", path, handle) // PATCH is a shortcut for router.Handle("PATCH", path, handle)
func PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().PATCH(relativePath, handlers...) return engine().PATCH(relativePath, handlers...)
} }
// PUT is a shortcut for router.Handle("PUT", path, handle) // PUT is a shortcut for router.Handle("PUT", path, handle)
func PUT(relativePath string, handlers ...HandlerFunc) IRoutes { func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().PUT(relativePath, handlers...) return engine().PUT(relativePath, handlers...)
} }
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().OPTIONS(relativePath, handlers...) return engine().OPTIONS(relativePath, handlers...)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle) // HEAD is a shortcut for router.Handle("HEAD", path, handle)
func HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().HEAD(relativePath, handlers...) return engine().HEAD(relativePath, handlers...)
} }
func Any(relativePath string, handlers ...HandlerFunc) IRoutes { func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().Any(relativePath, handlers...) return engine().Any(relativePath, handlers...)
} }
func StaticFile(relativePath, filepath string) IRoutes { func StaticFile(relativePath, filepath string) gin.IRoutes {
return engine().StaticFile(relativePath, filepath) return engine().StaticFile(relativePath, filepath)
} }
@ -103,18 +103,18 @@ func StaticFile(relativePath, filepath string) IRoutes {
// To use the operating system's file system implementation, // To use the operating system's file system implementation,
// use : // use :
// router.Static("/static", "/var/www") // router.Static("/static", "/var/www")
func Static(relativePath, root string) IRoutes { func Static(relativePath, root string) gin.IRoutes {
return engine().Static(relativePath, root) return engine().Static(relativePath, root)
} }
func StaticFS(relativePath string, fs http.FileSystem) IRoutes { func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
return engine().StaticFS(relativePath, fs) return engine().StaticFS(relativePath, fs)
} }
// Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be // 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... // 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. // For example, this is the right place for a logger or error management middleware.
func Use(middlewares ...HandlerFunc) IRoutes { func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
return engine().Use(middlewares...) return engine().Use(middlewares...)
} }

View File

@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) {
c, err := net.Dial("unix", "/tmp/unix_unit_test") c, err := net.Dial("unix", "/tmp/unix_unit_test")
assert.NoError(t, err) assert.NoError(t, err)
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c) scanner := bufio.NewScanner(c)
var response string var response string
for scanner.Scan() { for scanner.Scan() {

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"crypto/tls"
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
@ -21,9 +22,9 @@ func formatAsDate(t time.Time) string {
return fmt.Sprintf("%d/%02d/%02d", year, month, day) return fmt.Sprintf("%d/%02d/%02d", year, month, day)
} }
func setupHTMLFiles(t *testing.T) func() { func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
go func() { go func() {
SetMode(TestMode) SetMode(mode)
router := New() router := New()
router.Delims("{[{", "}]}") router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
@ -38,16 +39,21 @@ func setupHTMLFiles(t *testing.T) func() {
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), "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", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
} else {
router.Run(":8888") router.Run(":8888")
}
}() }()
t.Log("waiting 1 second for server startup") t.Log("waiting 1 second for server startup")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
return func() {} return func() {}
} }
func setupHTMLGlob(t *testing.T) func() { func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
go func() { go func() {
SetMode(DebugMode) SetMode(mode)
router := New() router := New()
router.Delims("{[{", "}]}") router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
@ -62,16 +68,20 @@ func setupHTMLGlob(t *testing.T) func() {
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), "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", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
} else {
router.Run(":8888") router.Run(":8888")
}
}() }()
t.Log("waiting 1 second for server startup") t.Log("waiting 1 second for server startup")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
return func() {} return func() {}
} }
//TODO
func TestLoadHTMLGlob(t *testing.T) { func TestLoadHTMLGlob(t *testing.T) {
td := setupHTMLGlob(t) td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/test") res, err := http.Get("http://127.0.0.1:8888/test")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) {
td() 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, "<h1>Hello world</h1>", 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, "<h1>Hello world</h1>", 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, "<h1>Hello world</h1>", string(resp[:]))
td()
}
func TestLoadHTMLGlobFromFuncMap(t *testing.T) { func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
time.Now() time.Now()
td := setupHTMLGlob(t) td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw") res, err := http.Get("http://127.0.0.1:8888/raw")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -97,9 +153,6 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
td() td()
} }
// func (engine *Engine) LoadHTMLFiles(files ...string) {
// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
func init() { func init() {
SetMode(TestMode) SetMode(TestMode)
} }
@ -117,17 +170,17 @@ func TestCreateEngine(t *testing.T) {
// router.LoadHTMLGlob("*.testtmpl") // router.LoadHTMLGlob("*.testtmpl")
// r := router.HTMLRender.(render.HTMLDebug) // r := router.HTMLRender.(render.HTMLDebug)
// assert.Empty(t, r.Files) // assert.Empty(t, r.Files)
// assert.Equal(t, r.Glob, "*.testtmpl") // assert.Equal(t, "*.testtmpl", r.Glob)
// //
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl") // router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
// r = router.HTMLRender.(render.HTMLDebug) // r = router.HTMLRender.(render.HTMLDebug)
// assert.Empty(t, r.Glob) // assert.Empty(t, r.Glob)
// assert.Equal(t, r.Files, []string{"index.html", "login.html"}) // assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
// SetMode(TestMode) // SetMode(TestMode)
// } // }
func TestLoadHTMLFiles(t *testing.T) { func TestLoadHTMLFiles(t *testing.T) {
td := setupHTMLFiles(t) td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test") res, err := http.Get("http://127.0.0.1:8888/test")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -138,9 +191,52 @@ func TestLoadHTMLFiles(t *testing.T) {
td() 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, "<h1>Hello world</h1>", 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, "<h1>Hello world</h1>", 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, "<h1>Hello world</h1>", string(resp[:]))
td()
}
func TestLoadHTMLFilesFuncMap(t *testing.T) { func TestLoadHTMLFilesFuncMap(t *testing.T) {
time.Now() time.Now()
td := setupHTMLFiles(t) td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw") res, err := http.Get("http://127.0.0.1:8888/raw")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -152,10 +248,6 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
td() td()
} }
func TestLoadHTMLReleaseMode(t *testing.T) {
}
func TestAddRoute(t *testing.T) { func TestAddRoute(t *testing.T) {
router := New() router := New()
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})

15
json/json.go Normal file
View File

@ -0,0 +1,15 @@
// 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
)

16
json/jsoniter.go Normal file
View File

@ -0,0 +1,16 @@
// 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
)

View File

@ -25,17 +25,17 @@ var (
disableColor = false disableColor = false
) )
// DisableConsoleColor disables color output in the console // DisableConsoleColor disables color output in the console.
func DisableConsoleColor() { func DisableConsoleColor() {
disableColor = true disableColor = true
} }
// ErrorLogger returns a handlerfunc for any error type // ErrorLogger returns a handlerfunc for any error type.
func ErrorLogger() HandlerFunc { func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny) return ErrorLoggerT(ErrorTypeAny)
} }
// ErrorLoggerT returns a handlerfunc for a given error type // ErrorLoggerT returns a handlerfunc for a given error type.
func ErrorLoggerT(typ ErrorType) HandlerFunc { func ErrorLoggerT(typ ErrorType) HandlerFunc {
return func(c *Context) { return func(c *Context) {
c.Next() c.Next()
@ -46,8 +46,8 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
} }
} }
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default gin.DefaultWriter = os.Stdout // By default gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc { func Logger() HandlerFunc {
return LoggerWithWriter(DefaultWriter) return LoggerWithWriter(DefaultWriter)
} }
@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
clientIP := c.ClientIP() clientIP := c.ClientIP()
method := c.Request.Method method := c.Request.Method
statusCode := c.Writer.Status() statusCode := c.Writer.Status()
var statusColor, methodColor string var statusColor, methodColor, resetColor string
if isTerm { if isTerm {
statusColor = colorForStatus(statusCode) statusColor = colorForStatus(statusCode)
methodColor = colorForMethod(method) methodColor = colorForMethod(method)
resetColor = reset
} }
comment := c.Errors.ByType(ErrorTypePrivate).String() comment := c.Errors.ByType(ErrorTypePrivate).String()
@ -102,12 +103,12 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
path = path + "?" + raw path = path + "?" + raw
} }
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
end.Format("2006/01/02 - 15:04:05"), end.Format("2006/01/02 - 15:04:05"),
statusColor, statusCode, reset, statusColor, statusCode, resetColor,
latency, latency,
clientIP, clientIP,
methodColor, method, reset, methodColor, method, resetColor,
path, path,
comment, comment,
) )

View File

@ -82,21 +82,21 @@ func TestLogger(t *testing.T) {
} }
func TestColorForMethod(t *testing.T) { func TestColorForMethod(t *testing.T) {
assert.Equal(t, colorForMethod("GET"), string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue")
assert.Equal(t, colorForMethod("POST"), string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), "post should be cyan") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan")
assert.Equal(t, colorForMethod("PUT"), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "put should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow")
assert.Equal(t, colorForMethod("DELETE"), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "delete should be red") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red")
assert.Equal(t, colorForMethod("PATCH"), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "patch should be green") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green")
assert.Equal(t, colorForMethod("HEAD"), string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), "head should be magenta") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta")
assert.Equal(t, colorForMethod("OPTIONS"), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "options should be white") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white")
assert.Equal(t, colorForMethod("TRACE"), string([]byte{27, 91, 48, 109}), "trace is not defined and should be the reset color") 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) { func TestColorForStatus(t *testing.T) {
assert.Equal(t, colorForStatus(200), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green")
assert.Equal(t, colorForStatus(301), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "3xx should be white") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white")
assert.Equal(t, colorForStatus(404), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow")
assert.Equal(t, colorForStatus(2), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "other things should be red") 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) { func TestErrorLogger(t *testing.T) {

View File

@ -37,8 +37,8 @@ func TestMiddlewareGeneralCase(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Equal(t, signature, "ACDB") assert.Equal(t, "ACDB", signature)
} }
func TestMiddlewareNoRoute(t *testing.T) { func TestMiddlewareNoRoute(t *testing.T) {
@ -73,8 +73,8 @@ func TestMiddlewareNoRoute(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
assert.Equal(t, signature, "ACEGHFDB") assert.Equal(t, "ACEGHFDB", signature)
} }
func TestMiddlewareNoMethodEnabled(t *testing.T) { func TestMiddlewareNoMethodEnabled(t *testing.T) {
@ -110,8 +110,8 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 405) assert.Equal(t, 405, w.Code)
assert.Equal(t, signature, "ACEGHFDB") assert.Equal(t, "ACEGHFDB", signature)
} }
func TestMiddlewareNoMethodDisabled(t *testing.T) { func TestMiddlewareNoMethodDisabled(t *testing.T) {
@ -147,8 +147,8 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
assert.Equal(t, signature, "AC X DB") assert.Equal(t, "AC X DB", signature)
} }
func TestMiddlewareAbort(t *testing.T) { func TestMiddlewareAbort(t *testing.T) {
@ -173,8 +173,8 @@ func TestMiddlewareAbort(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 401) assert.Equal(t, 401, w.Code)
assert.Equal(t, signature, "ACD") assert.Equal(t, "ACD", signature)
} }
func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
@ -195,8 +195,8 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 410) assert.Equal(t, 410, w.Code)
assert.Equal(t, signature, "ACB") assert.Equal(t, "ACB", signature)
} }
// TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as // TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as
@ -218,8 +218,8 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, w.Code, 500) assert.Equal(t, 500, w.Code)
assert.Equal(t, signature, "A") assert.Equal(t, "A", signature)
} }
func TestMiddlewareWrite(t *testing.T) { func TestMiddlewareWrite(t *testing.T) {

15
mode.go
View File

@ -14,9 +14,9 @@ import (
const ENV_GIN_MODE = "GIN_MODE" const ENV_GIN_MODE = "GIN_MODE"
const ( const (
DebugMode string = "debug" DebugMode = "debug"
ReleaseMode string = "release" ReleaseMode = "release"
TestMode string = "test" TestMode = "test"
) )
const ( const (
debugCode = iota debugCode = iota
@ -39,16 +39,12 @@ var modeName = DebugMode
func init() { func init() {
mode := os.Getenv(ENV_GIN_MODE) mode := os.Getenv(ENV_GIN_MODE)
if len(mode) == 0 {
SetMode(DebugMode)
} else {
SetMode(mode) SetMode(mode)
} }
}
func SetMode(value string) { func SetMode(value string) {
switch value { switch value {
case DebugMode: case DebugMode, "":
ginMode = debugCode ginMode = debugCode
case ReleaseMode: case ReleaseMode:
ginMode = releaseCode ginMode = releaseCode
@ -57,6 +53,9 @@ func SetMode(value string) {
default: default:
panic("gin mode unknown: " + value) panic("gin mode unknown: " + value)
} }
if value == "" {
value = DebugMode
}
modeName = value modeName = value
} }

View File

@ -17,21 +17,25 @@ func init() {
} }
func TestSetMode(t *testing.T) { func TestSetMode(t *testing.T) {
assert.Equal(t, ginMode, testCode) assert.Equal(t, testCode, ginMode)
assert.Equal(t, Mode(), TestMode) assert.Equal(t, TestMode, Mode())
os.Unsetenv(ENV_GIN_MODE) os.Unsetenv(ENV_GIN_MODE)
SetMode("")
assert.Equal(t, debugCode, ginMode)
assert.Equal(t, DebugMode, Mode())
SetMode(DebugMode) SetMode(DebugMode)
assert.Equal(t, ginMode, debugCode) assert.Equal(t, debugCode, ginMode)
assert.Equal(t, Mode(), DebugMode) assert.Equal(t, DebugMode, Mode())
SetMode(ReleaseMode) SetMode(ReleaseMode)
assert.Equal(t, ginMode, releaseCode) assert.Equal(t, releaseCode, ginMode)
assert.Equal(t, Mode(), ReleaseMode) assert.Equal(t, ReleaseMode, Mode())
SetMode(TestMode) SetMode(TestMode)
assert.Equal(t, ginMode, testCode) assert.Equal(t, testCode, ginMode)
assert.Equal(t, Mode(), TestMode) assert.Equal(t, TestMode, Mode())
assert.Panics(t, func() { SetMode("unknown") }) assert.Panics(t, func() { SetMode("unknown") })
} }

View File

@ -17,7 +17,7 @@ package gin
// 4. Eliminate .. elements that begin a rooted path: // 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path. // that is, replace "/.." by "/" at the beginning of a path.
// //
// If the result of this process is an empty string, "/" is returned // If the result of this process is an empty string, "/" is returned.
func cleanPath(p string) string { func cleanPath(p string) string {
// Turn empty string into "/" // Turn empty string into "/"
if p == "" { if p == "" {
@ -109,7 +109,7 @@ func cleanPath(p string) string {
return string(buf[:w]) return string(buf[:w])
} }
// internal helper to lazily create a buffer if necessary // internal helper to lazily create a buffer if necessary.
func bufApp(buf *[]byte, s string, w int, c byte) { func bufApp(buf *[]byte, s string, w int, c byte) {
if *buf == nil { if *buf == nil {
if s[w] == c { if s[w] == c {

View File

@ -67,8 +67,8 @@ var cleanTests = []struct {
func TestPathClean(t *testing.T) { func TestPathClean(t *testing.T) {
for _, test := range cleanTests { for _, test := range cleanTests {
assert.Equal(t, cleanPath(test.path), test.result) assert.Equal(t, test.result, cleanPath(test.path))
assert.Equal(t, cleanPath(test.result), test.result) assert.Equal(t, test.result, cleanPath(test.result))
} }
} }

View File

@ -12,6 +12,7 @@ import (
"log" "log"
"net/http/httputil" "net/http/httputil"
"runtime" "runtime"
"time"
) )
var ( var (
@ -26,6 +27,7 @@ func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter) 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 { func RecoveryWithWriter(out io.Writer) HandlerFunc {
var logger *log.Logger var logger *log.Logger
if out != nil { if out != nil {
@ -37,7 +39,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
if logger != nil { if logger != nil {
stack := stack(3) stack := stack(3)
httprequest, _ := httputil.DumpRequest(c.Request, false) httprequest, _ := httputil.DumpRequest(c.Request, false)
logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset) logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset)
} }
c.AbortWithStatus(500) c.AbortWithStatus(500)
} }
@ -46,7 +48,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
} }
} }
// stack returns a nicely formated stack frame, skipping skip frames // stack returns a nicely formatted stack frame, skipping skip frames.
func stack(skip int) []byte { func stack(skip int) []byte {
buf := new(bytes.Buffer) // the returned data buf := new(bytes.Buffer) // the returned data
// As we loop, we open files and read them. These variables record the currently // As we loop, we open files and read them. These variables record the currently
@ -106,3 +108,8 @@ func function(pc uintptr) []byte {
name = bytes.Replace(name, centerDot, dot, -1) name = bytes.Replace(name, centerDot, dot, -1)
return name return name
} }
func timeFormat(t time.Time) string {
var timeString = t.Format("2006/01/02 - 15:04:05")
return timeString
}

View File

@ -22,7 +22,7 @@ func TestPanicInHandler(t *testing.T) {
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := performRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, w.Code, 500) assert.Equal(t, 500, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
assert.Contains(t, buffer.String(), "TestPanicInHandler") assert.Contains(t, buffer.String(), "TestPanicInHandler")
@ -39,5 +39,5 @@ func TestPanicWithAbort(t *testing.T) {
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := performRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, w.Code, 400) assert.Equal(t, 400, w.Code)
} }

View File

@ -11,7 +11,7 @@ type Data struct {
Data []byte Data []byte
} }
// Render (Data) writes data with custom ContentType // Render (Data) writes data with custom ContentType.
func (r Data) Render(w http.ResponseWriter) (err error) { func (r Data) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w) r.WriteContentType(w)
_, err = w.Write(r.Data) _, err = w.Write(r.Data)

View File

@ -60,7 +60,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
if len(r.Files) > 0 { if len(r.Files) > 0 {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
} }
if len(r.Glob) > 0 { if r.Glob != "" {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(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") panic("the HTML debug render was created without files or glob pattern")
@ -69,7 +69,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
func (r HTML) Render(w http.ResponseWriter) error { func (r HTML) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
if len(r.Name) == 0 { if r.Name == "" {
return r.Template.Execute(w, r.Data) return r.Template.Execute(w, r.Data)
} }
return r.Template.ExecuteTemplate(w, r.Name, r.Data) return r.Template.ExecuteTemplate(w, r.Name, r.Data)

View File

@ -8,11 +8,9 @@ import (
"bytes" "bytes"
"net/http" "net/http"
"github.com/json-iterator/go" "github.com/gin-gonic/gin/json"
) )
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type JSON struct { type JSON struct {
Data interface{} Data interface{}
} }

View File

@ -7,7 +7,9 @@ package render
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"errors"
"html/template" "html/template"
"net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -24,6 +26,9 @@ func TestRenderMsgPack(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(MsgPack{data}).WriteContentType(w)
assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8")
err := (MsgPack{data}).Render(w) err := (MsgPack{data}).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -45,6 +50,9 @@ func TestRenderJSON(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(JSON{data}).WriteContentType(w)
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
err := (JSON{data}).Render(w) err := (JSON{data}).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -52,6 +60,14 @@ func TestRenderJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) 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) { func TestRenderIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]interface{}{
@ -66,12 +82,24 @@ func TestRenderIndentedJSON(t *testing.T) {
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
} }
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) { func TestRenderSecureJSON(t *testing.T) {
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]interface{}{
"foo": "bar", "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) err1 := (SecureJSON{"while(1);", data}).Render(w1)
assert.NoError(t, err1) assert.NoError(t, err1)
@ -91,6 +119,15 @@ func TestRenderSecureJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) 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)
}
type xmlmap map[string]interface{} type xmlmap map[string]interface{}
// Allows type H to be used with xml.Marshal // Allows type H to be used with xml.Marshal
@ -111,10 +148,38 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err return err
} }
} }
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
return err return e.EncodeToken(xml.EndElement{Name: start.Name})
} }
return nil
func TestRenderYAML(t *testing.T) {
w := httptest.NewRecorder()
data := `
a : Easy!
b:
c: 2
d: [3, 4]
`
(YAML{data}).WriteContentType(w)
assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8")
err := (YAML{data}).Render(w)
assert.NoError(t, err)
assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n")
assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8")
}
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) { func TestRenderXML(t *testing.T) {
@ -123,6 +188,9 @@ func TestRenderXML(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(XML{data}).WriteContentType(w)
assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8")
err := (XML{data}).Render(w) err := (XML{data}).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -131,7 +199,30 @@ func TestRenderXML(t *testing.T) {
} }
func TestRenderRedirect(t *testing.T) { func TestRenderRedirect(t *testing.T) {
// TODO req, err := http.NewRequest("GET", "/test-redirect", nil)
assert.NoError(t, err)
data1 := Redirect{
Code: 301,
Request: req,
Location: "/new/location",
}
w := httptest.NewRecorder()
err = data1.Render(w)
assert.NoError(t, err)
data2 := Redirect{
Code: 200,
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) { func TestRenderData(t *testing.T) {
@ -151,6 +242,12 @@ func TestRenderData(t *testing.T) {
func TestRenderString(t *testing.T) { func TestRenderString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
(String{
Format: "hello %s %d",
Data: []interface{}{},
}).WriteContentType(w)
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
err := (String{ err := (String{
Format: "hola %s %d", Format: "hola %s %d",
Data: []interface{}{"manu", 2}, Data: []interface{}{"manu", 2},
@ -161,6 +258,19 @@ func TestRenderString(t *testing.T) {
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
} }
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, w.Body.String(), "hola %s %d")
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
}
func TestRenderHTMLTemplate(t *testing.T) { func TestRenderHTMLTemplate(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
@ -176,3 +286,64 @@ func TestRenderHTMLTemplate(t *testing.T) {
assert.Equal(t, w.Body.String(), "Hello alexandernyquist") assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
} }
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, w.Body.String(), "Hello alexandernyquist")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
}
func TestRenderHTMLDebugFiles(t *testing.T) {
w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/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, w.Body.String(), "<h1>Hello thinkerou</h1>")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
}
func TestRenderHTMLDebugGlob(t *testing.T) {
w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: nil,
Glob: "../fixtures/basic/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, w.Body.String(), "<h1>Hello thinkerou</h1>")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
}
func TestRenderHTMLDebugPanics(t *testing.T) {
htmlRender := HTMLDebug{Files: nil,
Glob: "",
Delims: Delims{"{{", "}}"},
FuncMap: nil,
}
assert.Panics(t, func() { htmlRender.Instance("", nil) })
}

View File

@ -95,7 +95,7 @@ func (w *responseWriter) Written() bool {
return w.size != noWritten return w.size != noWritten
} }
// Hijack implements the http.Hijacker interface // Hijack implements the http.Hijacker interface.
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if w.size < 0 { if w.size < 0 {
w.size = 0 w.size = 0
@ -103,12 +103,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack() return w.ResponseWriter.(http.Hijacker).Hijack()
} }
// CloseNotify implements the http.CloseNotify interface // CloseNotify implements the http.CloseNotify interface.
func (w *responseWriter) CloseNotify() <-chan bool { func (w *responseWriter) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify() return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
// Flush implements the http.Flush interface // Flush implements the http.Flush interface.
func (w *responseWriter) Flush() { func (w *responseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush() w.ResponseWriter.(http.Flusher).Flush()
} }

View File

@ -34,11 +34,11 @@ func TestResponseWriterReset(t *testing.T) {
var w ResponseWriter = writer var w ResponseWriter = writer
writer.reset(testWritter) writer.reset(testWritter)
assert.Equal(t, writer.size, -1) assert.Equal(t, -1, writer.size)
assert.Equal(t, writer.status, 200) assert.Equal(t, 200, writer.status)
assert.Equal(t, writer.ResponseWriter, testWritter) assert.Equal(t, testWritter, writer.ResponseWriter)
assert.Equal(t, w.Size(), -1) assert.Equal(t, -1, w.Size())
assert.Equal(t, w.Status(), 200) assert.Equal(t, 200, w.Status())
assert.False(t, w.Written()) assert.False(t, w.Written())
} }
@ -50,11 +50,11 @@ func TestResponseWriterWriteHeader(t *testing.T) {
w.WriteHeader(300) w.WriteHeader(300)
assert.False(t, w.Written()) assert.False(t, w.Written())
assert.Equal(t, w.Status(), 300) assert.Equal(t, 300, w.Status())
assert.NotEqual(t, testWritter.Code, 300) assert.NotEqual(t, testWritter.Code, 300)
w.WriteHeader(-1) w.WriteHeader(-1)
assert.Equal(t, w.Status(), 300) assert.Equal(t, 300, w.Status())
} }
func TestResponseWriterWriteHeadersNow(t *testing.T) { func TestResponseWriterWriteHeadersNow(t *testing.T) {
@ -67,12 +67,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
w.WriteHeaderNow() w.WriteHeaderNow()
assert.True(t, w.Written()) assert.True(t, w.Written())
assert.Equal(t, w.Size(), 0) assert.Equal(t, 0, w.Size())
assert.Equal(t, testWritter.Code, 300) assert.Equal(t, 300, testWritter.Code)
writer.size = 10 writer.size = 10
w.WriteHeaderNow() w.WriteHeaderNow()
assert.Equal(t, w.Size(), 10) assert.Equal(t, 10, w.Size())
} }
func TestResponseWriterWrite(t *testing.T) { func TestResponseWriterWrite(t *testing.T) {
@ -82,17 +82,17 @@ func TestResponseWriterWrite(t *testing.T) {
w := ResponseWriter(writer) w := ResponseWriter(writer)
n, err := w.Write([]byte("hola")) n, err := w.Write([]byte("hola"))
assert.Equal(t, n, 4) assert.Equal(t, 4, n)
assert.Equal(t, w.Size(), 4) assert.Equal(t, 4, w.Size())
assert.Equal(t, w.Status(), 200) assert.Equal(t, 200, w.Status())
assert.Equal(t, testWritter.Code, 200) assert.Equal(t, 200, testWritter.Code)
assert.Equal(t, testWritter.Body.String(), "hola") assert.Equal(t, "hola", testWritter.Body.String())
assert.NoError(t, err) assert.NoError(t, err)
n, err = w.Write([]byte(" adios")) n, err = w.Write([]byte(" adios"))
assert.Equal(t, n, 6) assert.Equal(t, 6, n)
assert.Equal(t, w.Size(), 10) assert.Equal(t, 10, w.Size())
assert.Equal(t, testWritter.Body.String(), "hola adios") assert.Equal(t, "hola adios", testWritter.Body.String())
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -35,7 +35,7 @@ type IRoutes interface {
} }
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
// and an array of handlers (middleware) // and an array of handlers (middleware).
type RouterGroup struct { type RouterGroup struct {
Handlers HandlersChain Handlers HandlersChain
basePath string basePath string
@ -89,43 +89,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
return group.handle(httpMethod, relativePath, handlers) return group.handle(httpMethod, relativePath, handlers)
} }
// POST is a shortcut for router.Handle("POST", path, handle) // POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("POST", relativePath, handlers) return group.handle("POST", relativePath, handlers)
} }
// GET is a shortcut for router.Handle("GET", path, handle) // GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers) return group.handle("GET", relativePath, handlers)
} }
// DELETE is a shortcut for router.Handle("DELETE", path, handle) // DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("DELETE", relativePath, handlers) return group.handle("DELETE", relativePath, handlers)
} }
// PATCH is a shortcut for router.Handle("PATCH", path, handle) // PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("PATCH", relativePath, handlers) return group.handle("PATCH", relativePath, handlers)
} }
// PUT is a shortcut for router.Handle("PUT", path, handle) // PUT is a shortcut for router.Handle("PUT", path, handle).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("PUT", relativePath, handlers) return group.handle("PUT", relativePath, handlers)
} }
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("OPTIONS", relativePath, handlers) return group.handle("OPTIONS", relativePath, handlers)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle) // HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("HEAD", relativePath, handlers) return group.handle("HEAD", relativePath, handlers)
} }
// Any registers a route that matches all the HTTP methods. // Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
group.handle("GET", relativePath, handlers) group.handle("GET", relativePath, handlers)
group.handle("POST", relativePath, handlers) group.handle("POST", relativePath, handlers)

View File

@ -20,15 +20,15 @@ func TestRouterGroupBasic(t *testing.T) {
group.Use(func(c *Context) {}) group.Use(func(c *Context) {})
assert.Len(t, group.Handlers, 2) assert.Len(t, group.Handlers, 2)
assert.Equal(t, group.BasePath(), "/hola") assert.Equal(t, "/hola", group.BasePath())
assert.Equal(t, group.engine, router) assert.Equal(t, router, group.engine)
group2 := group.Group("manu") group2 := group.Group("manu")
group2.Use(func(c *Context) {}, func(c *Context) {}) group2.Use(func(c *Context) {}, func(c *Context) {})
assert.Len(t, group2.Handlers, 4) assert.Len(t, group2.Handlers, 4)
assert.Equal(t, group2.BasePath(), "/hola/manu") assert.Equal(t, "/hola/manu", group2.BasePath())
assert.Equal(t, group2.engine, router) assert.Equal(t, router, group2.engine)
} }
func TestRouterGroupBasicHandle(t *testing.T) { func TestRouterGroupBasicHandle(t *testing.T) {
@ -44,10 +44,10 @@ func TestRouterGroupBasicHandle(t *testing.T) {
func performRequestInGroup(t *testing.T, method string) { func performRequestInGroup(t *testing.T, method string) {
router := New() router := New()
v1 := router.Group("v1", func(c *Context) {}) v1 := router.Group("v1", func(c *Context) {})
assert.Equal(t, v1.BasePath(), "/v1") assert.Equal(t, "/v1", v1.BasePath())
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {}) login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
assert.Equal(t, login.BasePath(), "/v1/login/") assert.Equal(t, "/v1/login/", login.BasePath())
handler := func(c *Context) { handler := func(c *Context) {
c.String(400, "the method was %s and index %d", c.Request.Method, c.index) c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
@ -80,12 +80,12 @@ func performRequestInGroup(t *testing.T, method string) {
} }
w := performRequest(router, method, "/v1/login/test") w := performRequest(router, method, "/v1/login/test")
assert.Equal(t, w.Code, 400) assert.Equal(t, 400, w.Code)
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3") assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
w = performRequest(router, method, "/v1/test") w = performRequest(router, method, "/v1/test")
assert.Equal(t, w.Code, 400) assert.Equal(t, 400, w.Code)
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1") assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
} }
func TestRouterGroupInvalidStatic(t *testing.T) { func TestRouterGroupInvalidStatic(t *testing.T) {

View File

@ -36,7 +36,7 @@ func testRouteOK(method string, t *testing.T) {
w := performRequest(r, method, "/test") w := performRequest(r, method, "/test")
assert.True(t, passed) assert.True(t, passed)
assert.Equal(t, w.Code, http.StatusOK) assert.Equal(t, http.StatusOK, w.Code)
performRequest(r, method, "/test2") performRequest(r, method, "/test2")
assert.True(t, passedAny) assert.True(t, passedAny)
@ -53,7 +53,7 @@ func testRouteNotOK(method string, t *testing.T) {
w := performRequest(router, method, "/test") w := performRequest(router, method, "/test")
assert.False(t, passed) assert.False(t, passed)
assert.Equal(t, w.Code, http.StatusNotFound) assert.Equal(t, http.StatusNotFound, w.Code)
} }
// TestSingleRouteOK tests that POST route is correctly invoked. // TestSingleRouteOK tests that POST route is correctly invoked.
@ -74,7 +74,7 @@ func testRouteNotOK2(method string, t *testing.T) {
w := performRequest(router, method, "/test") w := performRequest(router, method, "/test")
assert.False(t, passed) assert.False(t, passed)
assert.Equal(t, w.Code, http.StatusMethodNotAllowed) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
} }
func TestRouterMethod(t *testing.T) { func TestRouterMethod(t *testing.T) {
@ -93,8 +93,8 @@ func TestRouterMethod(t *testing.T) {
w := performRequest(router, "PUT", "/hey") w := performRequest(router, "PUT", "/hey")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Equal(t, w.Body.String(), "called") assert.Equal(t, "called", w.Body.String())
} }
func TestRouterGroupRouteOK(t *testing.T) { func TestRouterGroupRouteOK(t *testing.T) {
@ -143,43 +143,43 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
router.PUT("/path4/", func(c *Context) {}) router.PUT("/path4/", func(c *Context) {})
w := performRequest(router, "GET", "/path/") w := performRequest(router, "GET", "/path/")
assert.Equal(t, w.Header().Get("Location"), "/path") assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, w.Code, 301) assert.Equal(t, 301, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, w.Header().Get("Location"), "/path2/") assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, w.Code, 301) assert.Equal(t, 301, w.Code)
w = performRequest(router, "POST", "/path3/") w = performRequest(router, "POST", "/path3/")
assert.Equal(t, w.Header().Get("Location"), "/path3") assert.Equal(t, "/path3", w.Header().Get("Location"))
assert.Equal(t, w.Code, 307) assert.Equal(t, 307, w.Code)
w = performRequest(router, "PUT", "/path4") w = performRequest(router, "PUT", "/path4")
assert.Equal(t, w.Header().Get("Location"), "/path4/") assert.Equal(t, "/path4/", w.Header().Get("Location"))
assert.Equal(t, w.Code, 307) assert.Equal(t, 307, w.Code)
w = performRequest(router, "GET", "/path") w = performRequest(router, "GET", "/path")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
w = performRequest(router, "GET", "/path2/") w = performRequest(router, "GET", "/path2/")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
w = performRequest(router, "POST", "/path3") w = performRequest(router, "POST", "/path3")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
w = performRequest(router, "PUT", "/path4/") w = performRequest(router, "PUT", "/path4/")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
router.RedirectTrailingSlash = false router.RedirectTrailingSlash = false
w = performRequest(router, "GET", "/path/") w = performRequest(router, "GET", "/path/")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
w = performRequest(router, "POST", "/path3/") w = performRequest(router, "POST", "/path3/")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
w = performRequest(router, "PUT", "/path4") w = performRequest(router, "PUT", "/path4")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
} }
func TestRouteRedirectFixedPath(t *testing.T) { func TestRouteRedirectFixedPath(t *testing.T) {
@ -193,20 +193,20 @@ func TestRouteRedirectFixedPath(t *testing.T) {
router.POST("/Path4/", func(c *Context) {}) router.POST("/Path4/", func(c *Context) {})
w := performRequest(router, "GET", "/PATH") w := performRequest(router, "GET", "/PATH")
assert.Equal(t, w.Header().Get("Location"), "/path") assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, w.Code, 301) assert.Equal(t, 301, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, w.Header().Get("Location"), "/Path2") assert.Equal(t, "/Path2", w.Header().Get("Location"))
assert.Equal(t, w.Code, 301) assert.Equal(t, 301, w.Code)
w = performRequest(router, "POST", "/path3") w = performRequest(router, "POST", "/path3")
assert.Equal(t, w.Header().Get("Location"), "/PATH3") assert.Equal(t, "/PATH3", w.Header().Get("Location"))
assert.Equal(t, w.Code, 307) assert.Equal(t, 307, w.Code)
w = performRequest(router, "POST", "/path4") w = performRequest(router, "POST", "/path4")
assert.Equal(t, w.Header().Get("Location"), "/Path4/") assert.Equal(t, "/Path4/", w.Header().Get("Location"))
assert.Equal(t, w.Code, 307) assert.Equal(t, 307, w.Code)
} }
// TestContextParamsGet tests that a parameter can be parsed from the URL. // TestContextParamsGet tests that a parameter can be parsed from the URL.
@ -236,10 +236,10 @@ func TestRouteParamsByName(t *testing.T) {
w := performRequest(router, "GET", "/test/john/smith/is/super/great") w := performRequest(router, "GET", "/test/john/smith/is/super/great")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Equal(t, name, "john") assert.Equal(t, "john", name)
assert.Equal(t, lastName, "smith") assert.Equal(t, "smith", lastName)
assert.Equal(t, wild, "/is/super/great") assert.Equal(t, "/is/super/great", wild)
} }
// TestHandleStaticFile - ensure the static file handles properly // TestHandleStaticFile - ensure the static file handles properly
@ -265,15 +265,15 @@ func TestRouteStaticFile(t *testing.T) {
w2 := performRequest(router, "GET", "/result") w2 := performRequest(router, "GET", "/result")
assert.Equal(t, w, w2) assert.Equal(t, w, w2)
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Equal(t, w.Body.String(), "Gin Web Framework") assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
w3 := performRequest(router, "HEAD", "/using_static/"+filename) w3 := performRequest(router, "HEAD", "/using_static/"+filename)
w4 := performRequest(router, "HEAD", "/result") w4 := performRequest(router, "HEAD", "/result")
assert.Equal(t, w3, w4) assert.Equal(t, w3, w4)
assert.Equal(t, w3.Code, 200) assert.Equal(t, 200, w3.Code)
} }
// TestHandleStaticDir - ensure the root/sub dir handles properly // TestHandleStaticDir - ensure the root/sub dir handles properly
@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "gin.go") assert.Contains(t, w.Body.String(), "gin.go")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
// TestHandleHeadToDir - ensure the root/sub dir handles properly // TestHandleHeadToDir - ensure the root/sub dir handles properly
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go") assert.NotContains(t, w.Body.String(), "gin.go")
} }
@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
w := performRequest(router, "GET", "/gin.go") w := performRequest(router, "GET", "/gin.go")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "package gin") assert.Contains(t, w.Body.String(), "package gin")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") 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.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
assert.Equal(t, w.HeaderMap.Get("Expires"), "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, w.HeaderMap.Get("x-GIN"), "Gin Framework") assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN"))
} }
func TestRouteNotAllowedEnabled(t *testing.T) { func TestRouteNotAllowedEnabled(t *testing.T) {
@ -323,14 +323,14 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
router.HandleMethodNotAllowed = true router.HandleMethodNotAllowed = true
router.POST("/path", func(c *Context) {}) router.POST("/path", func(c *Context) {})
w := performRequest(router, "GET", "/path") w := performRequest(router, "GET", "/path")
assert.Equal(t, w.Code, http.StatusMethodNotAllowed) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
router.NoMethod(func(c *Context) { router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText") c.String(http.StatusTeapot, "responseText")
}) })
w = performRequest(router, "GET", "/path") w = performRequest(router, "GET", "/path")
assert.Equal(t, w.Body.String(), "responseText") assert.Equal(t, "responseText", w.Body.String())
assert.Equal(t, w.Code, http.StatusTeapot) assert.Equal(t, http.StatusTeapot, w.Code)
} }
func TestRouteNotAllowedDisabled(t *testing.T) { func TestRouteNotAllowedDisabled(t *testing.T) {
@ -338,14 +338,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
router.HandleMethodNotAllowed = false router.HandleMethodNotAllowed = false
router.POST("/path", func(c *Context) {}) router.POST("/path", func(c *Context) {})
w := performRequest(router, "GET", "/path") w := performRequest(router, "GET", "/path")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
router.NoMethod(func(c *Context) { router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText") c.String(http.StatusTeapot, "responseText")
}) })
w = performRequest(router, "GET", "/path") w = performRequest(router, "GET", "/path")
assert.Equal(t, w.Body.String(), "404 page not found") assert.Equal(t, "404 page not found", w.Body.String())
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
} }
func TestRouterNotFound(t *testing.T) { func TestRouterNotFound(t *testing.T) {
@ -358,23 +358,23 @@ func TestRouterNotFound(t *testing.T) {
testRoutes := []struct { testRoutes := []struct {
route string route string
code int code int
header string location string
}{ }{
{"/path/", 301, "map[Location:[/path]]"}, // TSR -/ {"/path/", 301, "/path"}, // TSR -/
{"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ {"/dir", 301, "/dir/"}, // TSR +/
{"", 301, "map[Location:[/]]"}, // TSR +/ {"", 301, "/"}, // TSR +/
{"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case {"/PATH", 301, "/path"}, // Fixed Case
{"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case {"/DIR/", 301, "/dir/"}, // Fixed Case
{"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ {"/PATH/", 301, "/path"}, // Fixed Case -/
{"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ {"/DIR", 301, "/dir/"}, // Fixed Case +/
{"/../path", 301, "map[Location:[/path]]"}, // CleanPath {"/../path", 301, "/path"}, // CleanPath
{"/nope", 404, ""}, // NotFound {"/nope", 404, ""}, // NotFound
} }
for _, tr := range testRoutes { for _, tr := range testRoutes {
w := performRequest(router, "GET", tr.route) w := performRequest(router, "GET", tr.route)
assert.Equal(t, w.Code, tr.code) assert.Equal(t, tr.code, w.Code)
if w.Code != 404 { if w.Code != 404 {
assert.Equal(t, fmt.Sprint(w.Header()), tr.header) assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
} }
} }
@ -385,20 +385,20 @@ func TestRouterNotFound(t *testing.T) {
notFound = true notFound = true
}) })
w := performRequest(router, "GET", "/nope") w := performRequest(router, "GET", "/nope")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
assert.True(t, notFound) assert.True(t, notFound)
// Test other method than GET (want 307 instead of 301) // Test other method than GET (want 307 instead of 301)
router.PATCH("/path", func(c *Context) {}) router.PATCH("/path", func(c *Context) {})
w = performRequest(router, "PATCH", "/path/") w = performRequest(router, "PATCH", "/path/")
assert.Equal(t, w.Code, 307) assert.Equal(t, 307, w.Code)
assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]") assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
// Test special case where no node for the prefix "/" exists // Test special case where no node for the prefix "/" exists
router = New() router = New()
router.GET("/a", func(c *Context) {}) router.GET("/a", func(c *Context) {})
w = performRequest(router, "GET", "/") w = performRequest(router, "GET", "/")
assert.Equal(t, w.Code, 404) assert.Equal(t, 404, w.Code)
} }
func TestRouteRawPath(t *testing.T) { func TestRouteRawPath(t *testing.T) {
@ -409,15 +409,15 @@ func TestRouteRawPath(t *testing.T) {
name := c.Params.ByName("name") name := c.Params.ByName("name")
num := c.Params.ByName("num") num := c.Params.ByName("num")
assert.Equal(t, c.Param("name"), name) assert.Equal(t, name, c.Param("name"))
assert.Equal(t, c.Param("num"), num) assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some/Other/Project", name) assert.Equal(t, "Some/Other/Project", name)
assert.Equal(t, "222", num) assert.Equal(t, "222", num)
}) })
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
} }
func TestRouteRawPathNoUnescape(t *testing.T) { func TestRouteRawPathNoUnescape(t *testing.T) {
@ -429,15 +429,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
name := c.Params.ByName("name") name := c.Params.ByName("name")
num := c.Params.ByName("num") num := c.Params.ByName("num")
assert.Equal(t, c.Param("name"), name) assert.Equal(t, name, c.Param("name"))
assert.Equal(t, c.Param("num"), num) assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some%2FOther%2FProject", name) assert.Equal(t, "Some%2FOther%2FProject", name)
assert.Equal(t, "333", num) assert.Equal(t, "333", num)
}) })
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
assert.Equal(t, w.Code, 200) assert.Equal(t, 200, w.Code)
} }
func TestRouteServeErrorWithWriteHeader(t *testing.T) { func TestRouteServeErrorWithWriteHeader(t *testing.T) {

16
tree.go
View File

@ -87,16 +87,16 @@ const (
type node struct { type node struct {
path string path string
wildChild bool
nType nodeType
maxParams uint8
indices string indices string
children []*node children []*node
handlers HandlersChain handlers HandlersChain
priority uint32 priority uint32
nType nodeType
maxParams uint8
wildChild bool
} }
// increments priority of the given child and reorders if necessary // increments priority of the given child and reorders if necessary.
func (n *node) incrementChildPrio(pos int) int { func (n *node) incrementChildPrio(pos int) int {
n.children[pos].priority++ n.children[pos].priority++
prio := n.children[pos].priority prio := n.children[pos].priority
@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree
// Nothing found. // Nothing found.
// We can recommend to redirect to the same URL without a // We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path. // trailing slash if a leaf exists for that path.
tsr = (path == "/" && n.handlers != nil) tsr = path == "/" && n.handlers != nil
return return
} }
@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree
} }
// ... but we can't // ... but we can't
tsr = (len(path) == end+1) tsr = len(path) == end+1
return return
} }
@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree
// No handle found. Check if a handle for this path + a // No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation // trailing slash exists for TSR recommendation
n = n.children[0] n = n.children[0]
tsr = (n.path == "/" && n.handlers != nil) tsr = n.path == "/" && n.handlers != nil
} }
return return
@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
// Nothing found. We can recommend to redirect to the same URL // Nothing found. We can recommend to redirect to the same URL
// without a trailing slash if a leaf exists for that path // without a trailing slash if a leaf exists for that path
found = (fixTrailingSlash && path == "/" && n.handlers != nil) found = fixTrailingSlash && path == "/" && n.handlers != nil
return return
} }

View File

@ -5,22 +5,11 @@
package gin package gin
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
) )
func printChildren(n *node, prefix string) {
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
for l := len(n.path); l > 0; l-- {
prefix += " "
}
for _, child := range n.children {
printChildren(child, prefix)
}
}
// Used as a workaround since we can't compare functions or their addressses // Used as a workaround since we can't compare functions or their addressses
var fakeHandlerValue string var fakeHandlerValue string

View File

@ -33,21 +33,26 @@ func Bind(val interface{}) HandlerFunc {
} }
} }
// WrapF is a helper function for wrapping http.HandlerFunc
// Returns a Gin middleware
func WrapF(f http.HandlerFunc) HandlerFunc { func WrapF(f http.HandlerFunc) HandlerFunc {
return func(c *Context) { return func(c *Context) {
f(c.Writer, c.Request) f(c.Writer, c.Request)
} }
} }
// WrapH is a helper function for wrapping http.Handler
// Returns a Gin middleware
func WrapH(h http.Handler) HandlerFunc { func WrapH(h http.Handler) HandlerFunc {
return func(c *Context) { return func(c *Context) {
h.ServeHTTP(c.Writer, c.Request) h.ServeHTTP(c.Writer, c.Request)
} }
} }
// H is a shortcut for map[string]interface{}
type H map[string]interface{} type H map[string]interface{}
// MarshalXML allows type H to be used with xml.Marshal // MarshalXML allows type H to be used with xml.Marshal.
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Name = xml.Name{ start.Name = xml.Name{
Space: "", Space: "",
@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err return err
} }
} }
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
return err return e.EncodeToken(xml.EndElement{Name: start.Name})
}
return nil
} }
func assert1(guard bool, text string) { func assert1(guard bool, text string) {
@ -103,7 +106,7 @@ func parseAccept(acceptHeader string) []string {
if index := strings.IndexByte(part, ';'); index >= 0 { if index := strings.IndexByte(part, ';'); index >= 0 {
part = part[0:index] part = part[0:index]
} }
if part = strings.TrimSpace(part); len(part) > 0 { if part = strings.TrimSpace(part); part != "" {
out = append(out, part) out = append(out, part)
} }
} }
@ -111,11 +114,10 @@ func parseAccept(acceptHeader string) []string {
} }
func lastChar(str string) uint8 { func lastChar(str string) uint8 {
size := len(str) if str == "" {
if size == 0 {
panic("The length of the string can't be 0") panic("The length of the string can't be 0")
} }
return str[size-1] return str[len(str)-1]
} }
func nameOfFunction(f interface{}) string { func nameOfFunction(f interface{}) string {
@ -123,7 +125,7 @@ func nameOfFunction(f interface{}) string {
} }
func joinPaths(absolutePath, relativePath string) string { func joinPaths(absolutePath, relativePath string) string {
if len(relativePath) == 0 { if relativePath == "" {
return absolutePath return absolutePath
} }
@ -138,7 +140,7 @@ func joinPaths(absolutePath, relativePath string) string {
func resolveAddress(addr []string) string { func resolveAddress(addr []string) string {
switch len(addr) { switch len(addr) {
case 0: case 0:
if port := os.Getenv("PORT"); len(port) > 0 { if port := os.Getenv("PORT"); port != "" {
debugPrint("Environment variable PORT=\"%s\"", port) debugPrint("Environment variable PORT=\"%s\"", port)
return ":" + port return ":" + port
} }

View File

@ -21,8 +21,8 @@ type testStruct struct {
} }
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
assert.Equal(t.T, req.Method, "POST") assert.Equal(t.T, "POST", req.Method)
assert.Equal(t.T, req.URL.Path, "/path") assert.Equal(t.T, "/path", req.URL.Path)
w.WriteHeader(500) w.WriteHeader(500)
fmt.Fprint(w, "hello") fmt.Fprint(w, "hello")
} }
@ -31,50 +31,50 @@ func TestWrap(t *testing.T) {
router := New() router := New()
router.POST("/path", WrapH(&testStruct{t})) router.POST("/path", WrapH(&testStruct{t}))
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
assert.Equal(t, req.Method, "GET") assert.Equal(t, "GET", req.Method)
assert.Equal(t, req.URL.Path, "/path2") assert.Equal(t, "/path2", req.URL.Path)
w.WriteHeader(400) w.WriteHeader(400)
fmt.Fprint(w, "hola!") fmt.Fprint(w, "hola!")
})) }))
w := performRequest(router, "POST", "/path") w := performRequest(router, "POST", "/path")
assert.Equal(t, w.Code, 500) assert.Equal(t, 500, w.Code)
assert.Equal(t, w.Body.String(), "hello") assert.Equal(t, "hello", w.Body.String())
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, w.Code, 400) assert.Equal(t, 400, w.Code)
assert.Equal(t, w.Body.String(), "hola!") assert.Equal(t, "hola!", w.Body.String())
} }
func TestLastChar(t *testing.T) { func TestLastChar(t *testing.T) {
assert.Equal(t, lastChar("hola"), uint8('a')) assert.Equal(t, uint8('a'), lastChar("hola"))
assert.Equal(t, lastChar("adios"), uint8('s')) assert.Equal(t, uint8('s'), lastChar("adios"))
assert.Panics(t, func() { lastChar("") }) assert.Panics(t, func() { lastChar("") })
} }
func TestParseAccept(t *testing.T) { func TestParseAccept(t *testing.T) {
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
assert.Len(t, parts, 4) assert.Len(t, parts, 4)
assert.Equal(t, parts[0], "text/html") assert.Equal(t, "text/html", parts[0])
assert.Equal(t, parts[1], "application/xhtml+xml") assert.Equal(t, "application/xhtml+xml", parts[1])
assert.Equal(t, parts[2], "application/xml") assert.Equal(t, "application/xml", parts[2])
assert.Equal(t, parts[3], "*/*") assert.Equal(t, "*/*", parts[3])
} }
func TestChooseData(t *testing.T) { func TestChooseData(t *testing.T) {
A := "a" A := "a"
B := "b" B := "b"
assert.Equal(t, chooseData(A, B), A) assert.Equal(t, A, chooseData(A, B))
assert.Equal(t, chooseData(nil, B), B) assert.Equal(t, B, chooseData(nil, B))
assert.Panics(t, func() { chooseData(nil, nil) }) assert.Panics(t, func() { chooseData(nil, nil) })
} }
func TestFilterFlags(t *testing.T) { func TestFilterFlags(t *testing.T) {
result := filterFlags("text/html ") result := filterFlags("text/html ")
assert.Equal(t, result, "text/html") assert.Equal(t, "text/html", result)
result = filterFlags("text/html;") result = filterFlags("text/html;")
assert.Equal(t, result, "text/html") assert.Equal(t, "text/html", result)
} }
func TestFunctionName(t *testing.T) { func TestFunctionName(t *testing.T) {
@ -86,16 +86,16 @@ func somefunction() {
} }
func TestJoinPaths(t *testing.T) { func TestJoinPaths(t *testing.T) {
assert.Equal(t, joinPaths("", ""), "") assert.Equal(t, "", joinPaths("", ""))
assert.Equal(t, joinPaths("", "/"), "/") assert.Equal(t, "/", joinPaths("", "/"))
assert.Equal(t, joinPaths("/a", ""), "/a") assert.Equal(t, "/a", joinPaths("/a", ""))
assert.Equal(t, joinPaths("/a/", ""), "/a/") assert.Equal(t, "/a/", joinPaths("/a/", ""))
assert.Equal(t, joinPaths("/a/", "/"), "/a/") assert.Equal(t, "/a/", joinPaths("/a/", "/"))
assert.Equal(t, joinPaths("/a", "/"), "/a/") assert.Equal(t, "/a/", joinPaths("/a", "/"))
assert.Equal(t, joinPaths("/a", "/hola"), "/a/hola") assert.Equal(t, "/a/hola", joinPaths("/a", "/hola"))
assert.Equal(t, joinPaths("/a/", "/hola"), "/a/hola") assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola"))
assert.Equal(t, joinPaths("/a/", "/hola/"), "/a/hola/") assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/"))
assert.Equal(t, joinPaths("/a/", "/hola//"), "/a/hola/") assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//"))
} }
type bindTestStruct struct { type bindTestStruct struct {
@ -113,8 +113,8 @@ func TestBindMiddleware(t *testing.T) {
}) })
performRequest(router, "GET", "/?foo=hola&bar=10") performRequest(router, "GET", "/?foo=hola&bar=10")
assert.True(t, called) assert.True(t, called)
assert.Equal(t, value.Foo, "hola") assert.Equal(t, "hola", value.Foo)
assert.Equal(t, value.Bar, 10) assert.Equal(t, 10, value.Bar)
called = false called = false
performRequest(router, "GET", "/?foo=hola&bar=1") performRequest(router, "GET", "/?foo=hola&bar=1")

24
vendor/vendor.json vendored
View File

@ -22,10 +22,10 @@
"revisionTime": "2017-01-09T09:34:21Z" "revisionTime": "2017-01-09T09:34:21Z"
}, },
{ {
"checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=", "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=",
"path": "github.com/gin-gonic/autotls", "path": "github.com/gin-gonic/autotls",
"revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d", "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815",
"revisionTime": "2017-04-16T09:39:34Z" "revisionTime": "2017-09-16T16:54:15Z"
}, },
{ {
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
@ -34,10 +34,10 @@
"revisionTime": "2017-06-01T23:02:30Z" "revisionTime": "2017-06-01T23:02:30Z"
}, },
{ {
"checksumSHA1": "WFJPa8cL6nzQU3yA1iN+gmaqrSU=", "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=",
"path": "github.com/json-iterator/go", "path": "github.com/json-iterator/go",
"revision": "4b33139ad07fda872cb378bb4218b2fab74ce62b", "revision": "36b14963da70d11297d313183d7e6388c8510e1e",
"revisionTime": "2017-07-12T09:56:51Z" "revisionTime": "2017-08-29T15:58:51Z"
}, },
{ {
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
@ -65,6 +65,12 @@
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
"revisionTime": "2016-09-25T22:06:09Z" "revisionTime": "2016-09-25T22:06:09Z"
}, },
{
"checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=",
"path": "github.com/thinkerou/favicon",
"revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93",
"revisionTime": "2017-07-10T14:05:20Z"
},
{ {
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
"path": "github.com/ugorji/go/codec", "path": "github.com/ugorji/go/codec",
@ -90,6 +96,12 @@
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
"revisionTime": "2016-10-18T08:54:36Z" "revisionTime": "2016-10-18T08:54:36Z"
}, },
{
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
"path": "golang.org/x/sync/errgroup",
"revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690",
"revisionTime": "2017-07-19T03:38:01Z"
},
{ {
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
"path": "golang.org/x/sys/unix", "path": "golang.org/x/sys/unix",