diff --git a/.gitignore b/.gitignore
index f3b636df..14dc8f20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ vendor/*
!vendor/vendor.json
coverage.out
count.out
+test
diff --git a/.travis.yml b/.travis.yml
index 821ce8df..e9101568 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,10 +4,12 @@ go:
- 1.6.x
- 1.7.x
- 1.8.x
+ - 1.9.x
+ - 1.10.x
- master
git:
- depth: 3
+ depth: 10
install:
- make install
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index 6efe3ca4..9a7df86a 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -1,298 +1,604 @@
-**Machine:** intel i7 ivy bridge quad-core. 8GB RAM.
-**Date:** June 4th, 2015
-[https://github.com/gin-gonic/go-http-routing-benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
+
+## Benchmark System
+
+**VM HOST:** DigitalOcean
+**Machine:** 4 CPU, 8 GB RAM. Ubuntu 16.04.2 x64
+**Date:** July 19th, 2017
+**Go Version:** 1.8.3 linux/amd64
+**Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark)
+
+## Static Routes: 157
```
-BenchmarkAce_Param 5000000 372 ns/op 32 B/op 1 allocs/op
-BenchmarkBear_Param 1000000 1165 ns/op 424 B/op 5 allocs/op
-BenchmarkBeego_Param 1000000 2440 ns/op 720 B/op 10 allocs/op
-BenchmarkBone_Param 1000000 1067 ns/op 384 B/op 3 allocs/op
-BenchmarkDenco_Param 5000000 240 ns/op 32 B/op 1 allocs/op
-BenchmarkEcho_Param 10000000 130 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_Param 10000000 133 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_Param 1000000 1826 ns/op 656 B/op 9 allocs/op
-BenchmarkGoji_Param 2000000 957 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_Param 1000000 2021 ns/op 657 B/op 14 allocs/op
-BenchmarkGoRestful_Param 200000 8825 ns/op 2496 B/op 31 allocs/op
-BenchmarkGorillaMux_Param 500000 3340 ns/op 784 B/op 9 allocs/op
-BenchmarkHttpRouter_Param 10000000 152 ns/op 32 B/op 1 allocs/op
-BenchmarkHttpTreeMux_Param 2000000 717 ns/op 336 B/op 2 allocs/op
-BenchmarkKocha_Param 3000000 423 ns/op 56 B/op 3 allocs/op
-BenchmarkMacaron_Param 1000000 3410 ns/op 1104 B/op 11 allocs/op
-BenchmarkMartini_Param 200000 7101 ns/op 1152 B/op 12 allocs/op
-BenchmarkPat_Param 1000000 2040 ns/op 656 B/op 14 allocs/op
-BenchmarkPossum_Param 1000000 2048 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_Param 1000000 1144 ns/op 432 B/op 6 allocs/op
-BenchmarkRevel_Param 200000 6725 ns/op 1672 B/op 28 allocs/op
-BenchmarkRivet_Param 1000000 1121 ns/op 464 B/op 5 allocs/op
-BenchmarkTango_Param 1000000 1479 ns/op 256 B/op 10 allocs/op
-BenchmarkTigerTonic_Param 1000000 3393 ns/op 992 B/op 19 allocs/op
-BenchmarkTraffic_Param 300000 5525 ns/op 1984 B/op 23 allocs/op
-BenchmarkVulcan_Param 2000000 924 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_Param 1000000 1084 ns/op 368 B/op 3 allocs/op
-BenchmarkAce_Param5 3000000 614 ns/op 160 B/op 1 allocs/op
-BenchmarkBear_Param5 1000000 1617 ns/op 469 B/op 5 allocs/op
-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
-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
-BenchmarkGoji_Param5 1000000 1287 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_Param5 1000000 3670 ns/op 1105 B/op 17 allocs/op
-BenchmarkGoRestful_Param5 200000 10756 ns/op 2672 B/op 31 allocs/op
-BenchmarkGorillaMux_Param5 300000 5543 ns/op 912 B/op 9 allocs/op
-BenchmarkHttpRouter_Param5 5000000 403 ns/op 160 B/op 1 allocs/op
-BenchmarkHttpTreeMux_Param5 1000000 1089 ns/op 336 B/op 2 allocs/op
-BenchmarkKocha_Param5 1000000 1682 ns/op 440 B/op 10 allocs/op
-BenchmarkMacaron_Param5 300000 4596 ns/op 1376 B/op 14 allocs/op
-BenchmarkMartini_Param5 100000 15703 ns/op 1280 B/op 12 allocs/op
-BenchmarkPat_Param5 300000 5320 ns/op 1008 B/op 42 allocs/op
-BenchmarkPossum_Param5 1000000 2155 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_Param5 1000000 1559 ns/op 432 B/op 6 allocs/op
-BenchmarkRevel_Param5 200000 8184 ns/op 2024 B/op 35 allocs/op
-BenchmarkRivet_Param5 1000000 1914 ns/op 528 B/op 9 allocs/op
-BenchmarkTango_Param5 1000000 3280 ns/op 944 B/op 18 allocs/op
-BenchmarkTigerTonic_Param5 200000 11638 ns/op 2519 B/op 53 allocs/op
-BenchmarkTraffic_Param5 200000 8941 ns/op 2280 B/op 31 allocs/op
-BenchmarkVulcan_Param5 1000000 1279 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_Param5 1000000 1574 ns/op 416 B/op 3 allocs/op
-BenchmarkAce_Param20 1000000 1528 ns/op 640 B/op 1 allocs/op
-BenchmarkBear_Param20 300000 4906 ns/op 1633 B/op 5 allocs/op
-BenchmarkBeego_Param20 200000 10529 ns/op 3868 B/op 17 allocs/op
-BenchmarkBone_Param20 300000 7362 ns/op 2539 B/op 5 allocs/op
-BenchmarkDenco_Param20 1000000 1884 ns/op 640 B/op 1 allocs/op
-BenchmarkEcho_Param20 2000000 689 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_Param20 3000000 545 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_Param20 200000 9437 ns/op 3804 B/op 16 allocs/op
-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
-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
-BenchmarkKocha_Param20 300000 5335 ns/op 1808 B/op 27 allocs/op
-BenchmarkMacaron_Param20 200000 11325 ns/op 4252 B/op 18 allocs/op
-BenchmarkMartini_Param20 20000 64419 ns/op 3644 B/op 14 allocs/op
-BenchmarkPat_Param20 50000 24672 ns/op 4888 B/op 151 allocs/op
-BenchmarkPossum_Param20 1000000 2085 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_Param20 300000 6809 ns/op 2283 B/op 8 allocs/op
-BenchmarkRevel_Param20 100000 16600 ns/op 5551 B/op 54 allocs/op
-BenchmarkRivet_Param20 200000 8428 ns/op 2620 B/op 26 allocs/op
-BenchmarkTango_Param20 100000 16302 ns/op 8224 B/op 48 allocs/op
-BenchmarkTigerTonic_Param20 30000 46828 ns/op 10538 B/op 178 allocs/op
-BenchmarkTraffic_Param20 50000 28871 ns/op 7998 B/op 66 allocs/op
-BenchmarkVulcan_Param20 1000000 2267 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_Param20 300000 6828 ns/op 2507 B/op 5 allocs/op
-BenchmarkAce_ParamWrite 3000000 502 ns/op 40 B/op 2 allocs/op
-BenchmarkBear_ParamWrite 1000000 1303 ns/op 424 B/op 5 allocs/op
-BenchmarkBeego_ParamWrite 1000000 2489 ns/op 728 B/op 11 allocs/op
-BenchmarkBone_ParamWrite 1000000 1181 ns/op 384 B/op 3 allocs/op
-BenchmarkDenco_ParamWrite 5000000 315 ns/op 32 B/op 1 allocs/op
-BenchmarkEcho_ParamWrite 10000000 237 ns/op 8 B/op 1 allocs/op
-BenchmarkGin_ParamWrite 5000000 336 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_ParamWrite 1000000 2079 ns/op 664 B/op 10 allocs/op
-BenchmarkGoji_ParamWrite 1000000 1092 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_ParamWrite 1000000 3329 ns/op 1136 B/op 19 allocs/op
-BenchmarkGoRestful_ParamWrite 200000 9273 ns/op 2504 B/op 32 allocs/op
-BenchmarkGorillaMux_ParamWrite 500000 3919 ns/op 792 B/op 10 allocs/op
-BenchmarkHttpRouter_ParamWrite 10000000 223 ns/op 32 B/op 1 allocs/op
-BenchmarkHttpTreeMux_ParamWrite 2000000 788 ns/op 336 B/op 2 allocs/op
-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
-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
-BenchmarkRevel_ParamWrite 200000 8048 ns/op 2128 B/op 33 allocs/op
-BenchmarkRivet_ParamWrite 1000000 1393 ns/op 472 B/op 6 allocs/op
-BenchmarkTango_ParamWrite 2000000 819 ns/op 136 B/op 5 allocs/op
-BenchmarkTigerTonic_ParamWrite 300000 5860 ns/op 1440 B/op 25 allocs/op
-BenchmarkTraffic_ParamWrite 200000 7429 ns/op 2400 B/op 27 allocs/op
-BenchmarkVulcan_ParamWrite 2000000 972 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_ParamWrite 1000000 1226 ns/op 368 B/op 3 allocs/op
-BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op
-BenchmarkBear_GithubStatic 3000000 575 ns/op 88 B/op 3 allocs/op
-BenchmarkBeego_GithubStatic 1000000 1561 ns/op 368 B/op 7 allocs/op
-BenchmarkBone_GithubStatic 200000 12301 ns/op 2880 B/op 60 allocs/op
-BenchmarkDenco_GithubStatic 20000000 74.6 ns/op 0 B/op 0 allocs/op
-BenchmarkEcho_GithubStatic 10000000 176 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_GithubStatic 10000000 159 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GithubStatic 1000000 1116 ns/op 304 B/op 6 allocs/op
-BenchmarkGoji_GithubStatic 5000000 413 ns/op 0 B/op 0 allocs/op
-BenchmarkGoRestful_GithubStatic 30000 55200 ns/op 3520 B/op 36 allocs/op
-BenchmarkGoJsonRest_GithubStatic 1000000 1504 ns/op 337 B/op 12 allocs/op
-BenchmarkGorillaMux_GithubStatic 100000 23620 ns/op 464 B/op 8 allocs/op
-BenchmarkHttpRouter_GithubStatic 20000000 78.3 ns/op 0 B/op 0 allocs/op
-BenchmarkHttpTreeMux_GithubStatic 20000000 84.9 ns/op 0 B/op 0 allocs/op
-BenchmarkKocha_GithubStatic 20000000 111 ns/op 0 B/op 0 allocs/op
-BenchmarkMacaron_GithubStatic 1000000 2686 ns/op 752 B/op 8 allocs/op
-BenchmarkMartini_GithubStatic 100000 22244 ns/op 832 B/op 11 allocs/op
-BenchmarkPat_GithubStatic 100000 13278 ns/op 3648 B/op 76 allocs/op
-BenchmarkPossum_GithubStatic 1000000 1429 ns/op 480 B/op 4 allocs/op
-BenchmarkR2router_GithubStatic 2000000 726 ns/op 144 B/op 5 allocs/op
-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
-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
-BenchmarkZeus_GithubStatic 1000000 2522 ns/op 512 B/op 11 allocs/op
-BenchmarkAce_GithubParam 3000000 578 ns/op 96 B/op 1 allocs/op
-BenchmarkBear_GithubParam 1000000 1592 ns/op 464 B/op 5 allocs/op
-BenchmarkBeego_GithubParam 1000000 2891 ns/op 784 B/op 11 allocs/op
-BenchmarkBone_GithubParam 300000 6440 ns/op 1456 B/op 16 allocs/op
-BenchmarkDenco_GithubParam 3000000 514 ns/op 128 B/op 1 allocs/op
-BenchmarkEcho_GithubParam 5000000 292 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_GithubParam 10000000 242 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GithubParam 1000000 2343 ns/op 720 B/op 10 allocs/op
-BenchmarkGoji_GithubParam 1000000 1566 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_GithubParam 1000000 2828 ns/op 721 B/op 15 allocs/op
-BenchmarkGoRestful_GithubParam 10000 177711 ns/op 2816 B/op 35 allocs/op
-BenchmarkGorillaMux_GithubParam 100000 13591 ns/op 816 B/op 9 allocs/op
-BenchmarkHttpRouter_GithubParam 5000000 352 ns/op 96 B/op 1 allocs/op
-BenchmarkHttpTreeMux_GithubParam 2000000 973 ns/op 336 B/op 2 allocs/op
-BenchmarkKocha_GithubParam 2000000 889 ns/op 128 B/op 5 allocs/op
-BenchmarkMacaron_GithubParam 500000 4047 ns/op 1168 B/op 12 allocs/op
-BenchmarkMartini_GithubParam 50000 28982 ns/op 1184 B/op 12 allocs/op
-BenchmarkPat_GithubParam 200000 8747 ns/op 2480 B/op 56 allocs/op
-BenchmarkPossum_GithubParam 1000000 2158 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_GithubParam 1000000 1352 ns/op 432 B/op 6 allocs/op
-BenchmarkRevel_GithubParam 200000 7673 ns/op 1784 B/op 30 allocs/op
-BenchmarkRivet_GithubParam 1000000 1573 ns/op 480 B/op 6 allocs/op
-BenchmarkTango_GithubParam 1000000 2418 ns/op 480 B/op 13 allocs/op
-BenchmarkTigerTonic_GithubParam 300000 6048 ns/op 1440 B/op 28 allocs/op
-BenchmarkTraffic_GithubParam 100000 20143 ns/op 6024 B/op 55 allocs/op
-BenchmarkVulcan_GithubParam 1000000 2224 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_GithubParam 500000 4156 ns/op 1312 B/op 12 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
-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_GithubAll 30000 50991 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GithubAll 5000 449648 ns/op 133280 B/op 1889 allocs/op
-BenchmarkGoji_GithubAll 2000 689748 ns/op 56113 B/op 334 allocs/op
-BenchmarkGoJsonRest_GithubAll 5000 537769 ns/op 135995 B/op 2940 allocs/op
-BenchmarkGoRestful_GithubAll 100 18410628 ns/op 797236 B/op 7725 allocs/op
-BenchmarkGorillaMux_GithubAll 200 8036360 ns/op 153137 B/op 1791 allocs/op
-BenchmarkHttpRouter_GithubAll 20000 63506 ns/op 13792 B/op 167 allocs/op
-BenchmarkHttpTreeMux_GithubAll 10000 165927 ns/op 56112 B/op 334 allocs/op
-BenchmarkKocha_GithubAll 10000 171362 ns/op 23304 B/op 843 allocs/op
-BenchmarkMacaron_GithubAll 2000 817008 ns/op 224960 B/op 2315 allocs/op
-BenchmarkMartini_GithubAll 100 12609209 ns/op 237952 B/op 2686 allocs/op
-BenchmarkPat_GithubAll 300 4830398 ns/op 1504101 B/op 32222 allocs/op
-BenchmarkPossum_GithubAll 10000 301716 ns/op 97440 B/op 812 allocs/op
-BenchmarkR2router_GithubAll 10000 270691 ns/op 77328 B/op 1182 allocs/op
-BenchmarkRevel_GithubAll 1000 1491919 ns/op 345553 B/op 5918 allocs/op
-BenchmarkRivet_GithubAll 10000 283860 ns/op 84272 B/op 1079 allocs/op
-BenchmarkTango_GithubAll 5000 473821 ns/op 87078 B/op 2470 allocs/op
-BenchmarkTigerTonic_GithubAll 2000 1120131 ns/op 241088 B/op 6052 allocs/op
-BenchmarkTraffic_GithubAll 200 8708979 ns/op 2664762 B/op 22390 allocs/op
-BenchmarkVulcan_GithubAll 5000 353392 ns/op 19894 B/op 609 allocs/op
-BenchmarkZeus_GithubAll 2000 944234 ns/op 300688 B/op 2648 allocs/op
-BenchmarkAce_GPlusStatic 5000000 251 ns/op 0 B/op 0 allocs/op
-BenchmarkBear_GPlusStatic 3000000 415 ns/op 72 B/op 3 allocs/op
-BenchmarkBeego_GPlusStatic 1000000 1416 ns/op 352 B/op 7 allocs/op
-BenchmarkBone_GPlusStatic 10000000 192 ns/op 32 B/op 1 allocs/op
-BenchmarkDenco_GPlusStatic 30000000 47.6 ns/op 0 B/op 0 allocs/op
-BenchmarkEcho_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GPlusStatic 1000000 1035 ns/op 288 B/op 6 allocs/op
-BenchmarkGoji_GPlusStatic 5000000 304 ns/op 0 B/op 0 allocs/op
-BenchmarkGoJsonRest_GPlusStatic 1000000 1286 ns/op 337 B/op 12 allocs/op
-BenchmarkGoRestful_GPlusStatic 200000 9649 ns/op 2160 B/op 30 allocs/op
-BenchmarkGorillaMux_GPlusStatic 1000000 2346 ns/op 464 B/op 8 allocs/op
-BenchmarkHttpRouter_GPlusStatic 30000000 42.7 ns/op 0 B/op 0 allocs/op
-BenchmarkHttpTreeMux_GPlusStatic 30000000 49.5 ns/op 0 B/op 0 allocs/op
-BenchmarkKocha_GPlusStatic 20000000 74.8 ns/op 0 B/op 0 allocs/op
-BenchmarkMacaron_GPlusStatic 1000000 2520 ns/op 736 B/op 8 allocs/op
-BenchmarkMartini_GPlusStatic 300000 5310 ns/op 832 B/op 11 allocs/op
-BenchmarkPat_GPlusStatic 5000000 398 ns/op 96 B/op 2 allocs/op
-BenchmarkPossum_GPlusStatic 1000000 1434 ns/op 480 B/op 4 allocs/op
-BenchmarkR2router_GPlusStatic 2000000 646 ns/op 144 B/op 5 allocs/op
-BenchmarkRevel_GPlusStatic 300000 6172 ns/op 1272 B/op 25 allocs/op
-BenchmarkRivet_GPlusStatic 3000000 444 ns/op 112 B/op 2 allocs/op
-BenchmarkTango_GPlusStatic 1000000 1400 ns/op 208 B/op 10 allocs/op
-BenchmarkTigerTonic_GPlusStatic 10000000 213 ns/op 32 B/op 1 allocs/op
-BenchmarkTraffic_GPlusStatic 1000000 3091 ns/op 1208 B/op 16 allocs/op
-BenchmarkVulcan_GPlusStatic 2000000 863 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_GPlusStatic 10000000 237 ns/op 16 B/op 1 allocs/op
-BenchmarkAce_GPlusParam 3000000 435 ns/op 64 B/op 1 allocs/op
-BenchmarkBear_GPlusParam 1000000 1205 ns/op 448 B/op 5 allocs/op
-BenchmarkBeego_GPlusParam 1000000 2494 ns/op 720 B/op 10 allocs/op
-BenchmarkBone_GPlusParam 1000000 1126 ns/op 384 B/op 3 allocs/op
-BenchmarkDenco_GPlusParam 5000000 325 ns/op 64 B/op 1 allocs/op
-BenchmarkEcho_GPlusParam 10000000 168 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_GPlusParam 10000000 170 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GPlusParam 1000000 1895 ns/op 656 B/op 9 allocs/op
-BenchmarkGoji_GPlusParam 1000000 1071 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_GPlusParam 1000000 2282 ns/op 657 B/op 14 allocs/op
-BenchmarkGoRestful_GPlusParam 100000 19400 ns/op 2560 B/op 33 allocs/op
-BenchmarkGorillaMux_GPlusParam 500000 5001 ns/op 784 B/op 9 allocs/op
-BenchmarkHttpRouter_GPlusParam 10000000 240 ns/op 64 B/op 1 allocs/op
-BenchmarkHttpTreeMux_GPlusParam 2000000 797 ns/op 336 B/op 2 allocs/op
-BenchmarkKocha_GPlusParam 3000000 505 ns/op 56 B/op 3 allocs/op
-BenchmarkMacaron_GPlusParam 1000000 3668 ns/op 1104 B/op 11 allocs/op
-BenchmarkMartini_GPlusParam 200000 10672 ns/op 1152 B/op 12 allocs/op
-BenchmarkPat_GPlusParam 1000000 2376 ns/op 704 B/op 14 allocs/op
-BenchmarkPossum_GPlusParam 1000000 2090 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_GPlusParam 1000000 1233 ns/op 432 B/op 6 allocs/op
-BenchmarkRevel_GPlusParam 200000 6778 ns/op 1704 B/op 28 allocs/op
-BenchmarkRivet_GPlusParam 1000000 1279 ns/op 464 B/op 5 allocs/op
-BenchmarkTango_GPlusParam 1000000 1981 ns/op 272 B/op 10 allocs/op
-BenchmarkTigerTonic_GPlusParam 500000 3893 ns/op 1064 B/op 19 allocs/op
-BenchmarkTraffic_GPlusParam 200000 6585 ns/op 2000 B/op 23 allocs/op
-BenchmarkVulcan_GPlusParam 1000000 1233 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_GPlusParam 1000000 1350 ns/op 368 B/op 3 allocs/op
-BenchmarkAce_GPlus2Params 3000000 512 ns/op 64 B/op 1 allocs/op
-BenchmarkBear_GPlus2Params 1000000 1564 ns/op 464 B/op 5 allocs/op
-BenchmarkBeego_GPlus2Params 1000000 3043 ns/op 784 B/op 11 allocs/op
-BenchmarkBone_GPlus2Params 1000000 3152 ns/op 736 B/op 7 allocs/op
-BenchmarkDenco_GPlus2Params 3000000 431 ns/op 64 B/op 1 allocs/op
-BenchmarkEcho_GPlus2Params 5000000 247 ns/op 0 B/op 0 allocs/op
-BenchmarkGin_GPlus2Params 10000000 219 ns/op 0 B/op 0 allocs/op
-BenchmarkGocraftWeb_GPlus2Params 1000000 2363 ns/op 720 B/op 10 allocs/op
-BenchmarkGoji_GPlus2Params 1000000 1540 ns/op 336 B/op 2 allocs/op
-BenchmarkGoJsonRest_GPlus2Params 1000000 2872 ns/op 721 B/op 15 allocs/op
-BenchmarkGoRestful_GPlus2Params 100000 23030 ns/op 2720 B/op 35 allocs/op
-BenchmarkGorillaMux_GPlus2Params 200000 10516 ns/op 816 B/op 9 allocs/op
-BenchmarkHttpRouter_GPlus2Params 5000000 273 ns/op 64 B/op 1 allocs/op
-BenchmarkHttpTreeMux_GPlus2Params 2000000 939 ns/op 336 B/op 2 allocs/op
-BenchmarkKocha_GPlus2Params 2000000 844 ns/op 128 B/op 5 allocs/op
-BenchmarkMacaron_GPlus2Params 500000 3914 ns/op 1168 B/op 12 allocs/op
-BenchmarkMartini_GPlus2Params 50000 35759 ns/op 1280 B/op 16 allocs/op
-BenchmarkPat_GPlus2Params 200000 7089 ns/op 2304 B/op 41 allocs/op
-BenchmarkPossum_GPlus2Params 1000000 2093 ns/op 624 B/op 7 allocs/op
-BenchmarkR2router_GPlus2Params 1000000 1320 ns/op 432 B/op 6 allocs/op
-BenchmarkRevel_GPlus2Params 200000 7351 ns/op 1800 B/op 30 allocs/op
-BenchmarkRivet_GPlus2Params 1000000 1485 ns/op 480 B/op 6 allocs/op
-BenchmarkTango_GPlus2Params 1000000 2111 ns/op 448 B/op 12 allocs/op
-BenchmarkTigerTonic_GPlus2Params 300000 6271 ns/op 1528 B/op 28 allocs/op
-BenchmarkTraffic_GPlus2Params 100000 14886 ns/op 3312 B/op 34 allocs/op
-BenchmarkVulcan_GPlus2Params 1000000 1883 ns/op 98 B/op 3 allocs/op
-BenchmarkZeus_GPlus2Params 1000000 2686 ns/op 784 B/op 6 allocs/op
-BenchmarkAce_GPlusAll 300000 5912 ns/op 640 B/op 11 allocs/op
-BenchmarkBear_GPlusAll 100000 16448 ns/op 5072 B/op 61 allocs/op
-BenchmarkBeego_GPlusAll 50000 32916 ns/op 8976 B/op 129 allocs/op
-BenchmarkBone_GPlusAll 50000 25836 ns/op 6992 B/op 76 allocs/op
-BenchmarkDenco_GPlusAll 500000 4462 ns/op 672 B/op 11 allocs/op
-BenchmarkEcho_GPlusAll 500000 2806 ns/op 0 B/op 0 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
-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
-BenchmarkHttpRouter_GPlusAll 500000 3040 ns/op 640 B/op 11 allocs/op
-BenchmarkHttpTreeMux_GPlusAll 200000 9627 ns/op 3696 B/op 22 allocs/op
-BenchmarkKocha_GPlusAll 200000 8108 ns/op 976 B/op 43 allocs/op
-BenchmarkMacaron_GPlusAll 30000 48083 ns/op 13968 B/op 142 allocs/op
-BenchmarkMartini_GPlusAll 10000 196978 ns/op 15072 B/op 178 allocs/op
-BenchmarkPat_GPlusAll 30000 58865 ns/op 16880 B/op 343 allocs/op
-BenchmarkPossum_GPlusAll 100000 19685 ns/op 6240 B/op 52 allocs/op
-BenchmarkR2router_GPlusAll 100000 16251 ns/op 5040 B/op 76 allocs/op
-BenchmarkRevel_GPlusAll 20000 93489 ns/op 21656 B/op 368 allocs/op
-BenchmarkRivet_GPlusAll 100000 16907 ns/op 5408 B/op 64 allocs/op
+Gin: 30512 Bytes
+
+HttpServeMux: 17344 Bytes
+Ace: 30080 Bytes
+Bear: 30472 Bytes
+Beego: 96408 Bytes
+Bone: 37904 Bytes
+Denco: 10464 Bytes
+Echo: 73680 Bytes
+GocraftWeb: 55720 Bytes
+Goji: 27200 Bytes
+Gojiv2: 104464 Bytes
+GoJsonRest: 136472 Bytes
+GoRestful: 914904 Bytes
+GorillaMux: 675568 Bytes
+HttpRouter: 21128 Bytes
+HttpTreeMux: 73448 Bytes
+Kocha: 115072 Bytes
+LARS: 30120 Bytes
+Macaron: 37984 Bytes
+Martini: 310832 Bytes
+Pat: 20464 Bytes
+Possum: 91328 Bytes
+R2router: 23712 Bytes
+Rivet: 23880 Bytes
+Tango: 28008 Bytes
+TigerTonic: 80368 Bytes
+Traffic: 626480 Bytes
+Vulcan: 369064 Bytes
+```
+
+## GithubAPI Routes: 203
+
+```
+Gin: 52672 Bytes
+
+Ace: 48992 Bytes
+Bear: 161592 Bytes
+Beego: 147992 Bytes
+Bone: 97728 Bytes
+Denco: 36440 Bytes
+Echo: 95672 Bytes
+GocraftWeb: 95640 Bytes
+Goji: 86088 Bytes
+Gojiv2: 144392 Bytes
+GoJsonRest: 134648 Bytes
+GoRestful: 1410760 Bytes
+GorillaMux: 1509488 Bytes
+HttpRouter: 37464 Bytes
+HttpTreeMux: 78800 Bytes
+Kocha: 785408 Bytes
+LARS: 49032 Bytes
+Macaron: 132712 Bytes
+Martini: 564352 Bytes
+Pat: 21200 Bytes
+Possum: 83888 Bytes
+R2router: 47104 Bytes
+Rivet: 42840 Bytes
+Tango: 54584 Bytes
+TigerTonic: 96384 Bytes
+Traffic: 1061920 Bytes
+Vulcan: 465296 Bytes
+```
+
+## GPlusAPI Routes: 13
+
+```
+Gin: 3968 Bytes
+
+Ace: 3600 Bytes
+Bear: 7112 Bytes
+Beego: 10048 Bytes
+Bone: 6480 Bytes
+Denco: 3256 Bytes
+Echo: 9000 Bytes
+GocraftWeb: 7496 Bytes
+Goji: 2912 Bytes
+Gojiv2: 7376 Bytes
+GoJsonRest: 11544 Bytes
+GoRestful: 88776 Bytes
+GorillaMux: 71488 Bytes
+HttpRouter: 2712 Bytes
+HttpTreeMux: 7440 Bytes
+Kocha: 128880 Bytes
+LARS: 3640 Bytes
+Macaron: 8656 Bytes
+Martini: 23936 Bytes
+Pat: 1856 Bytes
+Possum: 7248 Bytes
+R2router: 3928 Bytes
+Rivet: 3064 Bytes
+Tango: 4912 Bytes
+TigerTonic: 9408 Bytes
+Traffic: 49472 Bytes
+Vulcan: 25496 Bytes
+```
+
+## ParseAPI Routes: 26
+
+```
+Gin: 6928 Bytes
+
+Ace: 6592 Bytes
+Bear: 12320 Bytes
+Beego: 18960 Bytes
+Bone: 11024 Bytes
+Denco: 4184 Bytes
+Echo: 11168 Bytes
+GocraftWeb: 12800 Bytes
+Goji: 5232 Bytes
+Gojiv2: 14464 Bytes
+GoJsonRest: 14216 Bytes
+GoRestful: 127368 Bytes
+GorillaMux: 123016 Bytes
+HttpRouter: 4976 Bytes
+HttpTreeMux: 7848 Bytes
+Kocha: 181712 Bytes
+LARS: 6632 Bytes
+Macaron: 13648 Bytes
+Martini: 45952 Bytes
+Pat: 2560 Bytes
+Possum: 9200 Bytes
+R2router: 7056 Bytes
+Rivet: 5680 Bytes
+Tango: 8664 Bytes
+TigerTonic: 9840 Bytes
+Traffic: 93480 Bytes
+Vulcan: 44504 Bytes
+```
+
+## Static Routes
+
+```
+BenchmarkGin_StaticAll 50000 34506 ns/op 0 B/op 0 allocs/op
+
+BenchmarkAce_StaticAll 30000 49657 ns/op 0 B/op 0 allocs/op
+BenchmarkHttpServeMux_StaticAll 2000 1183737 ns/op 96 B/op 8 allocs/op
+BenchmarkBeego_StaticAll 5000 412621 ns/op 57776 B/op 628 allocs/op
+BenchmarkBear_StaticAll 10000 149242 ns/op 20336 B/op 461 allocs/op
+BenchmarkBone_StaticAll 10000 118583 ns/op 0 B/op 0 allocs/op
+BenchmarkDenco_StaticAll 100000 13247 ns/op 0 B/op 0 allocs/op
+BenchmarkEcho_StaticAll 20000 79914 ns/op 5024 B/op 157 allocs/op
+BenchmarkGocraftWeb_StaticAll 10000 211823 ns/op 46440 B/op 785 allocs/op
+BenchmarkGoji_StaticAll 10000 109390 ns/op 0 B/op 0 allocs/op
+BenchmarkGojiv2_StaticAll 3000 415533 ns/op 145696 B/op 1099 allocs/op
+BenchmarkGoJsonRest_StaticAll 5000 364403 ns/op 51653 B/op 1727 allocs/op
+BenchmarkGoRestful_StaticAll 500 2578579 ns/op 314936 B/op 3144 allocs/op
+BenchmarkGorillaMux_StaticAll 500 2704856 ns/op 115648 B/op 1578 allocs/op
+BenchmarkHttpRouter_StaticAll 100000 18541 ns/op 0 B/op 0 allocs/op
+BenchmarkHttpTreeMux_StaticAll 100000 22332 ns/op 0 B/op 0 allocs/op
+BenchmarkKocha_StaticAll 50000 31176 ns/op 0 B/op 0 allocs/op
+BenchmarkLARS_StaticAll 50000 40840 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_StaticAll 5000 517656 ns/op 120576 B/op 1413 allocs/op
+BenchmarkMartini_StaticAll 300 4462289 ns/op 125442 B/op 1717 allocs/op
+BenchmarkPat_StaticAll 500 2157275 ns/op 533904 B/op 11123 allocs/op
+BenchmarkPossum_StaticAll 10000 254701 ns/op 65312 B/op 471 allocs/op
+BenchmarkR2router_StaticAll 10000 133956 ns/op 22608 B/op 628 allocs/op
+BenchmarkRivet_StaticAll 30000 46812 ns/op 0 B/op 0 allocs/op
+BenchmarkTango_StaticAll 5000 390613 ns/op 39225 B/op 1256 allocs/op
+BenchmarkTigerTonic_StaticAll 20000 88060 ns/op 7504 B/op 157 allocs/op
+BenchmarkTraffic_StaticAll 500 2910236 ns/op 729736 B/op 14287 allocs/op
+BenchmarkVulcan_StaticAll 5000 277366 ns/op 15386 B/op 471 allocs/op
+```
+
+## Micro Benchmarks
+
+```
+BenchmarkGin_Param 20000000 113 ns/op 0 B/op 0 allocs/op
+
+BenchmarkAce_Param 5000000 375 ns/op 32 B/op 1 allocs/op
+BenchmarkBear_Param 1000000 1709 ns/op 456 B/op 5 allocs/op
+BenchmarkBeego_Param 1000000 2484 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_Param 1000000 2391 ns/op 688 B/op 5 allocs/op
+BenchmarkDenco_Param 10000000 240 ns/op 32 B/op 1 allocs/op
+BenchmarkEcho_Param 5000000 366 ns/op 32 B/op 1 allocs/op
+BenchmarkGocraftWeb_Param 1000000 2343 ns/op 648 B/op 8 allocs/op
+BenchmarkGoji_Param 1000000 1197 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_Param 1000000 2771 ns/op 944 B/op 8 allocs/op
+BenchmarkGoJsonRest_Param 1000000 2993 ns/op 649 B/op 13 allocs/op
+BenchmarkGoRestful_Param 200000 8860 ns/op 2296 B/op 21 allocs/op
+BenchmarkGorillaMux_Param 500000 4461 ns/op 1056 B/op 11 allocs/op
+BenchmarkHttpRouter_Param 10000000 175 ns/op 32 B/op 1 allocs/op
+BenchmarkHttpTreeMux_Param 1000000 1167 ns/op 352 B/op 3 allocs/op
+BenchmarkKocha_Param 3000000 429 ns/op 56 B/op 3 allocs/op
+BenchmarkLARS_Param 10000000 134 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_Param 500000 4635 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_Param 200000 9933 ns/op 1072 B/op 10 allocs/op
+BenchmarkPat_Param 1000000 2929 ns/op 648 B/op 12 allocs/op
+BenchmarkPossum_Param 1000000 2503 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_Param 1000000 1507 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_Param 5000000 297 ns/op 48 B/op 1 allocs/op
+BenchmarkTango_Param 1000000 1862 ns/op 248 B/op 8 allocs/op
+BenchmarkTigerTonic_Param 500000 5660 ns/op 992 B/op 17 allocs/op
+BenchmarkTraffic_Param 200000 8408 ns/op 1960 B/op 21 allocs/op
+BenchmarkVulcan_Param 2000000 963 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_Param5 2000000 740 ns/op 160 B/op 1 allocs/op
+BenchmarkBear_Param5 1000000 2777 ns/op 501 B/op 5 allocs/op
+BenchmarkBeego_Param5 1000000 3740 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_Param5 1000000 2950 ns/op 736 B/op 5 allocs/op
+BenchmarkDenco_Param5 2000000 644 ns/op 160 B/op 1 allocs/op
+BenchmarkEcho_Param5 3000000 558 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_Param5 10000000 198 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_Param5 500000 3870 ns/op 920 B/op 11 allocs/op
+BenchmarkGoji_Param5 1000000 1746 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_Param5 1000000 3214 ns/op 1008 B/op 8 allocs/op
+BenchmarkGoJsonRest_Param5 500000 5509 ns/op 1097 B/op 16 allocs/op
+BenchmarkGoRestful_Param5 200000 11232 ns/op 2392 B/op 21 allocs/op
+BenchmarkGorillaMux_Param5 300000 7777 ns/op 1184 B/op 11 allocs/op
+BenchmarkHttpRouter_Param5 3000000 631 ns/op 160 B/op 1 allocs/op
+BenchmarkHttpTreeMux_Param5 1000000 2800 ns/op 576 B/op 6 allocs/op
+BenchmarkKocha_Param5 1000000 2053 ns/op 440 B/op 10 allocs/op
+BenchmarkLARS_Param5 10000000 232 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_Param5 500000 5888 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_Param5 200000 12807 ns/op 1232 B/op 11 allocs/op
+BenchmarkPat_Param5 300000 7320 ns/op 964 B/op 32 allocs/op
+BenchmarkPossum_Param5 1000000 2495 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_Param5 1000000 1844 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_Param5 2000000 935 ns/op 240 B/op 1 allocs/op
+BenchmarkTango_Param5 1000000 2327 ns/op 360 B/op 8 allocs/op
+BenchmarkTigerTonic_Param5 100000 18514 ns/op 2551 B/op 43 allocs/op
+BenchmarkTraffic_Param5 200000 11997 ns/op 2248 B/op 25 allocs/op
+BenchmarkVulcan_Param5 1000000 1333 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_Param20 1000000 2031 ns/op 640 B/op 1 allocs/op
+BenchmarkBear_Param20 200000 7285 ns/op 1664 B/op 5 allocs/op
+BenchmarkBeego_Param20 300000 6224 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_Param20 200000 8023 ns/op 1903 B/op 5 allocs/op
+BenchmarkDenco_Param20 1000000 2262 ns/op 640 B/op 1 allocs/op
+BenchmarkEcho_Param20 1000000 1387 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_Param20 3000000 503 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_Param20 100000 14408 ns/op 3795 B/op 15 allocs/op
+BenchmarkGoji_Param20 500000 5272 ns/op 1247 B/op 2 allocs/op
+BenchmarkGojiv2_Param20 1000000 4163 ns/op 1248 B/op 8 allocs/op
+BenchmarkGoJsonRest_Param20 100000 17866 ns/op 4485 B/op 20 allocs/op
+BenchmarkGoRestful_Param20 100000 21022 ns/op 4724 B/op 23 allocs/op
+BenchmarkGorillaMux_Param20 100000 17055 ns/op 3547 B/op 13 allocs/op
+BenchmarkHttpRouter_Param20 1000000 1748 ns/op 640 B/op 1 allocs/op
+BenchmarkHttpTreeMux_Param20 200000 12246 ns/op 3196 B/op 10 allocs/op
+BenchmarkKocha_Param20 300000 6861 ns/op 1808 B/op 27 allocs/op
+BenchmarkLARS_Param20 3000000 526 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_Param20 100000 13069 ns/op 2906 B/op 12 allocs/op
+BenchmarkMartini_Param20 100000 23602 ns/op 3597 B/op 13 allocs/op
+BenchmarkPat_Param20 50000 32143 ns/op 4688 B/op 111 allocs/op
+BenchmarkPossum_Param20 1000000 2396 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_Param20 200000 8907 ns/op 2283 B/op 7 allocs/op
+BenchmarkRivet_Param20 1000000 3280 ns/op 1024 B/op 1 allocs/op
+BenchmarkTango_Param20 500000 4640 ns/op 856 B/op 8 allocs/op
+BenchmarkTigerTonic_Param20 20000 67581 ns/op 10532 B/op 138 allocs/op
+BenchmarkTraffic_Param20 50000 40313 ns/op 7941 B/op 45 allocs/op
+BenchmarkVulcan_Param20 1000000 2264 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_ParamWrite 3000000 532 ns/op 40 B/op 2 allocs/op
+BenchmarkBear_ParamWrite 1000000 1778 ns/op 456 B/op 5 allocs/op
+BenchmarkBeego_ParamWrite 1000000 2596 ns/op 376 B/op 5 allocs/op
+BenchmarkBone_ParamWrite 1000000 2519 ns/op 688 B/op 5 allocs/op
+BenchmarkDenco_ParamWrite 5000000 411 ns/op 32 B/op 1 allocs/op
+BenchmarkEcho_ParamWrite 2000000 718 ns/op 40 B/op 2 allocs/op
+BenchmarkGin_ParamWrite 5000000 283 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_ParamWrite 1000000 2561 ns/op 656 B/op 9 allocs/op
+BenchmarkGoji_ParamWrite 1000000 1378 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_ParamWrite 1000000 3128 ns/op 976 B/op 10 allocs/op
+BenchmarkGoJsonRest_ParamWrite 500000 4446 ns/op 1128 B/op 18 allocs/op
+BenchmarkGoRestful_ParamWrite 200000 10291 ns/op 2304 B/op 22 allocs/op
+BenchmarkGorillaMux_ParamWrite 500000 5153 ns/op 1064 B/op 12 allocs/op
+BenchmarkHttpRouter_ParamWrite 5000000 263 ns/op 32 B/op 1 allocs/op
+BenchmarkHttpTreeMux_ParamWrite 1000000 1351 ns/op 352 B/op 3 allocs/op
+BenchmarkKocha_ParamWrite 3000000 538 ns/op 56 B/op 3 allocs/op
+BenchmarkLARS_ParamWrite 5000000 316 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_ParamWrite 500000 5756 ns/op 1160 B/op 14 allocs/op
+BenchmarkMartini_ParamWrite 200000 13097 ns/op 1176 B/op 14 allocs/op
+BenchmarkPat_ParamWrite 500000 4954 ns/op 1072 B/op 17 allocs/op
+BenchmarkPossum_ParamWrite 1000000 2499 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_ParamWrite 1000000 1531 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_ParamWrite 3000000 570 ns/op 112 B/op 2 allocs/op
+BenchmarkTango_ParamWrite 2000000 957 ns/op 136 B/op 4 allocs/op
+BenchmarkTigerTonic_ParamWrite 200000 7025 ns/op 1424 B/op 23 allocs/op
+BenchmarkTraffic_ParamWrite 200000 10112 ns/op 2384 B/op 25 allocs/op
+BenchmarkVulcan_ParamWrite 1000000 1006 ns/op 98 B/op 3 allocs/op
+```
+
+## GitHub
+
+```
+BenchmarkGin_GithubStatic 10000000 156 ns/op 0 B/op 0 allocs/op
+
+BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op
+BenchmarkBear_GithubStatic 2000000 893 ns/op 120 B/op 3 allocs/op
+BenchmarkBeego_GithubStatic 1000000 2491 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_GithubStatic 50000 25300 ns/op 2880 B/op 60 allocs/op
+BenchmarkDenco_GithubStatic 20000000 76.0 ns/op 0 B/op 0 allocs/op
+BenchmarkEcho_GithubStatic 2000000 516 ns/op 32 B/op 1 allocs/op
+BenchmarkGocraftWeb_GithubStatic 1000000 1448 ns/op 296 B/op 5 allocs/op
+BenchmarkGoji_GithubStatic 3000000 496 ns/op 0 B/op 0 allocs/op
+BenchmarkGojiv2_GithubStatic 1000000 2941 ns/op 928 B/op 7 allocs/op
+BenchmarkGoRestful_GithubStatic 100000 27256 ns/op 3224 B/op 22 allocs/op
+BenchmarkGoJsonRest_GithubStatic 1000000 2196 ns/op 329 B/op 11 allocs/op
+BenchmarkGorillaMux_GithubStatic 50000 31617 ns/op 736 B/op 10 allocs/op
+BenchmarkHttpRouter_GithubStatic 20000000 88.4 ns/op 0 B/op 0 allocs/op
+BenchmarkHttpTreeMux_GithubStatic 10000000 134 ns/op 0 B/op 0 allocs/op
+BenchmarkKocha_GithubStatic 20000000 113 ns/op 0 B/op 0 allocs/op
+BenchmarkLARS_GithubStatic 10000000 195 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GithubStatic 500000 3740 ns/op 768 B/op 9 allocs/op
+BenchmarkMartini_GithubStatic 50000 27673 ns/op 768 B/op 9 allocs/op
+BenchmarkPat_GithubStatic 100000 19470 ns/op 3648 B/op 76 allocs/op
+BenchmarkPossum_GithubStatic 1000000 1729 ns/op 416 B/op 3 allocs/op
+BenchmarkR2router_GithubStatic 2000000 879 ns/op 144 B/op 4 allocs/op
+BenchmarkRivet_GithubStatic 10000000 231 ns/op 0 B/op 0 allocs/op
+BenchmarkTango_GithubStatic 1000000 2325 ns/op 248 B/op 8 allocs/op
+BenchmarkTigerTonic_GithubStatic 3000000 610 ns/op 48 B/op 1 allocs/op
+BenchmarkTraffic_GithubStatic 20000 62973 ns/op 18904 B/op 148 allocs/op
+BenchmarkVulcan_GithubStatic 1000000 1447 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_GithubParam 2000000 686 ns/op 96 B/op 1 allocs/op
+BenchmarkBear_GithubParam 1000000 2155 ns/op 496 B/op 5 allocs/op
+BenchmarkBeego_GithubParam 1000000 2713 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_GithubParam 100000 15088 ns/op 1760 B/op 18 allocs/op
+BenchmarkDenco_GithubParam 2000000 629 ns/op 128 B/op 1 allocs/op
+BenchmarkEcho_GithubParam 2000000 653 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_GithubParam 5000000 255 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_GithubParam 1000000 3145 ns/op 712 B/op 9 allocs/op
+BenchmarkGoji_GithubParam 1000000 1916 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_GithubParam 1000000 3975 ns/op 1024 B/op 10 allocs/op
+BenchmarkGoJsonRest_GithubParam 300000 4134 ns/op 713 B/op 14 allocs/op
+BenchmarkGoRestful_GithubParam 50000 30782 ns/op 2360 B/op 21 allocs/op
+BenchmarkGorillaMux_GithubParam 100000 17148 ns/op 1088 B/op 11 allocs/op
+BenchmarkHttpRouter_GithubParam 3000000 523 ns/op 96 B/op 1 allocs/op
+BenchmarkHttpTreeMux_GithubParam 1000000 1671 ns/op 384 B/op 4 allocs/op
+BenchmarkKocha_GithubParam 1000000 1021 ns/op 128 B/op 5 allocs/op
+BenchmarkLARS_GithubParam 5000000 283 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GithubParam 500000 4270 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_GithubParam 100000 21728 ns/op 1152 B/op 11 allocs/op
+BenchmarkPat_GithubParam 200000 11208 ns/op 2464 B/op 48 allocs/op
+BenchmarkPossum_GithubParam 1000000 2334 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_GithubParam 1000000 1487 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_GithubParam 2000000 782 ns/op 96 B/op 1 allocs/op
+BenchmarkTango_GithubParam 1000000 2653 ns/op 344 B/op 8 allocs/op
+BenchmarkTigerTonic_GithubParam 300000 14073 ns/op 1440 B/op 24 allocs/op
+BenchmarkTraffic_GithubParam 50000 29164 ns/op 5992 B/op 52 allocs/op
+BenchmarkVulcan_GithubParam 1000000 2529 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_GithubAll 10000 134059 ns/op 13792 B/op 167 allocs/op
+BenchmarkBear_GithubAll 5000 534445 ns/op 86448 B/op 943 allocs/op
+BenchmarkBeego_GithubAll 3000 592444 ns/op 74705 B/op 812 allocs/op
+BenchmarkBone_GithubAll 200 6957308 ns/op 698784 B/op 8453 allocs/op
+BenchmarkDenco_GithubAll 10000 158819 ns/op 20224 B/op 167 allocs/op
+BenchmarkEcho_GithubAll 10000 154700 ns/op 6496 B/op 203 allocs/op
+BenchmarkGin_GithubAll 30000 48375 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_GithubAll 3000 570806 ns/op 131656 B/op 1686 allocs/op
+BenchmarkGoji_GithubAll 2000 818034 ns/op 56112 B/op 334 allocs/op
+BenchmarkGojiv2_GithubAll 2000 1213973 ns/op 274768 B/op 3712 allocs/op
+BenchmarkGoJsonRest_GithubAll 2000 785796 ns/op 134371 B/op 2737 allocs/op
+BenchmarkGoRestful_GithubAll 300 5238188 ns/op 689672 B/op 4519 allocs/op
+BenchmarkGorillaMux_GithubAll 100 10257726 ns/op 211840 B/op 2272 allocs/op
+BenchmarkHttpRouter_GithubAll 20000 105414 ns/op 13792 B/op 167 allocs/op
+BenchmarkHttpTreeMux_GithubAll 10000 319934 ns/op 65856 B/op 671 allocs/op
+BenchmarkKocha_GithubAll 10000 209442 ns/op 23304 B/op 843 allocs/op
+BenchmarkLARS_GithubAll 20000 62565 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GithubAll 2000 1161270 ns/op 204194 B/op 2000 allocs/op
+BenchmarkMartini_GithubAll 200 9991713 ns/op 226549 B/op 2325 allocs/op
+BenchmarkPat_GithubAll 200 5590793 ns/op 1499568 B/op 27435 allocs/op
+BenchmarkPossum_GithubAll 10000 319768 ns/op 84448 B/op 609 allocs/op
+BenchmarkR2router_GithubAll 10000 305134 ns/op 77328 B/op 979 allocs/op
+BenchmarkRivet_GithubAll 10000 132134 ns/op 16272 B/op 167 allocs/op
+BenchmarkTango_GithubAll 3000 552754 ns/op 63826 B/op 1618 allocs/op
+BenchmarkTigerTonic_GithubAll 1000 1439483 ns/op 239104 B/op 5374 allocs/op
+BenchmarkTraffic_GithubAll 100 11383067 ns/op 2659329 B/op 21848 allocs/op
+BenchmarkVulcan_GithubAll 5000 394253 ns/op 19894 B/op 609 allocs/op
+```
+
+## Google+
+
+```
+BenchmarkGin_GPlusStatic 10000000 183 ns/op 0 B/op 0 allocs/op
+
+BenchmarkAce_GPlusStatic 5000000 276 ns/op 0 B/op 0 allocs/op
+BenchmarkBear_GPlusStatic 2000000 652 ns/op 104 B/op 3 allocs/op
+BenchmarkBeego_GPlusStatic 1000000 2239 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_GPlusStatic 5000000 380 ns/op 32 B/op 1 allocs/op
+BenchmarkDenco_GPlusStatic 30000000 45.8 ns/op 0 B/op 0 allocs/op
+BenchmarkEcho_GPlusStatic 5000000 338 ns/op 32 B/op 1 allocs/op
+BenchmarkGocraftWeb_GPlusStatic 1000000 1158 ns/op 280 B/op 5 allocs/op
+BenchmarkGoji_GPlusStatic 5000000 331 ns/op 0 B/op 0 allocs/op
+BenchmarkGojiv2_GPlusStatic 1000000 2106 ns/op 928 B/op 7 allocs/op
+BenchmarkGoJsonRest_GPlusStatic 1000000 1626 ns/op 329 B/op 11 allocs/op
+BenchmarkGoRestful_GPlusStatic 300000 7598 ns/op 1976 B/op 20 allocs/op
+BenchmarkGorillaMux_GPlusStatic 1000000 2629 ns/op 736 B/op 10 allocs/op
+BenchmarkHttpRouter_GPlusStatic 30000000 52.5 ns/op 0 B/op 0 allocs/op
+BenchmarkHttpTreeMux_GPlusStatic 20000000 85.8 ns/op 0 B/op 0 allocs/op
+BenchmarkKocha_GPlusStatic 20000000 89.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLARS_GPlusStatic 10000000 162 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GPlusStatic 500000 3479 ns/op 768 B/op 9 allocs/op
+BenchmarkMartini_GPlusStatic 200000 9092 ns/op 768 B/op 9 allocs/op
+BenchmarkPat_GPlusStatic 3000000 493 ns/op 96 B/op 2 allocs/op
+BenchmarkPossum_GPlusStatic 1000000 1467 ns/op 416 B/op 3 allocs/op
+BenchmarkR2router_GPlusStatic 2000000 788 ns/op 144 B/op 4 allocs/op
+BenchmarkRivet_GPlusStatic 20000000 114 ns/op 0 B/op 0 allocs/op
+BenchmarkTango_GPlusStatic 1000000 1534 ns/op 200 B/op 8 allocs/op
+BenchmarkTigerTonic_GPlusStatic 5000000 282 ns/op 32 B/op 1 allocs/op
+BenchmarkTraffic_GPlusStatic 500000 3798 ns/op 1192 B/op 15 allocs/op
+BenchmarkVulcan_GPlusStatic 2000000 1125 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_GPlusParam 3000000 528 ns/op 64 B/op 1 allocs/op
+BenchmarkBear_GPlusParam 1000000 1570 ns/op 480 B/op 5 allocs/op
+BenchmarkBeego_GPlusParam 1000000 2369 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_GPlusParam 1000000 2028 ns/op 688 B/op 5 allocs/op
+BenchmarkDenco_GPlusParam 5000000 385 ns/op 64 B/op 1 allocs/op
+BenchmarkEcho_GPlusParam 3000000 441 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_GPlusParam 10000000 174 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_GPlusParam 1000000 2033 ns/op 648 B/op 8 allocs/op
+BenchmarkGoji_GPlusParam 1000000 1399 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_GPlusParam 1000000 2641 ns/op 944 B/op 8 allocs/op
+BenchmarkGoJsonRest_GPlusParam 1000000 2824 ns/op 649 B/op 13 allocs/op
+BenchmarkGoRestful_GPlusParam 200000 8875 ns/op 2296 B/op 21 allocs/op
+BenchmarkGorillaMux_GPlusParam 200000 6291 ns/op 1056 B/op 11 allocs/op
+BenchmarkHttpRouter_GPlusParam 5000000 316 ns/op 64 B/op 1 allocs/op
+BenchmarkHttpTreeMux_GPlusParam 1000000 1129 ns/op 352 B/op 3 allocs/op
+BenchmarkKocha_GPlusParam 3000000 538 ns/op 56 B/op 3 allocs/op
+BenchmarkLARS_GPlusParam 10000000 198 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GPlusParam 500000 3554 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_GPlusParam 200000 9831 ns/op 1072 B/op 10 allocs/op
+BenchmarkPat_GPlusParam 1000000 2706 ns/op 688 B/op 12 allocs/op
+BenchmarkPossum_GPlusParam 1000000 2297 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_GPlusParam 1000000 1318 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_GPlusParam 5000000 399 ns/op 48 B/op 1 allocs/op
+BenchmarkTango_GPlusParam 1000000 2070 ns/op 264 B/op 8 allocs/op
+BenchmarkTigerTonic_GPlusParam 500000 4853 ns/op 1056 B/op 17 allocs/op
+BenchmarkTraffic_GPlusParam 200000 8278 ns/op 1976 B/op 21 allocs/op
+BenchmarkVulcan_GPlusParam 1000000 1243 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_GPlus2Params 3000000 549 ns/op 64 B/op 1 allocs/op
+BenchmarkBear_GPlus2Params 1000000 2112 ns/op 496 B/op 5 allocs/op
+BenchmarkBeego_GPlus2Params 500000 2750 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_GPlus2Params 300000 7032 ns/op 1040 B/op 9 allocs/op
+BenchmarkDenco_GPlus2Params 3000000 502 ns/op 64 B/op 1 allocs/op
+BenchmarkEcho_GPlus2Params 3000000 641 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_GPlus2Params 5000000 250 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_GPlus2Params 1000000 2681 ns/op 712 B/op 9 allocs/op
+BenchmarkGoji_GPlus2Params 1000000 1926 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_GPlus2Params 500000 3996 ns/op 1024 B/op 11 allocs/op
+BenchmarkGoJsonRest_GPlus2Params 500000 3886 ns/op 713 B/op 14 allocs/op
+BenchmarkGoRestful_GPlus2Params 200000 10376 ns/op 2360 B/op 21 allocs/op
+BenchmarkGorillaMux_GPlus2Params 100000 14162 ns/op 1088 B/op 11 allocs/op
+BenchmarkHttpRouter_GPlus2Params 5000000 336 ns/op 64 B/op 1 allocs/op
+BenchmarkHttpTreeMux_GPlus2Params 1000000 1523 ns/op 384 B/op 4 allocs/op
+BenchmarkKocha_GPlus2Params 2000000 970 ns/op 128 B/op 5 allocs/op
+BenchmarkLARS_GPlus2Params 5000000 238 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GPlus2Params 500000 4016 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_GPlus2Params 100000 21253 ns/op 1200 B/op 13 allocs/op
+BenchmarkPat_GPlus2Params 200000 8632 ns/op 2256 B/op 34 allocs/op
+BenchmarkPossum_GPlus2Params 1000000 2171 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_GPlus2Params 1000000 1340 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_GPlus2Params 3000000 557 ns/op 96 B/op 1 allocs/op
+BenchmarkTango_GPlus2Params 1000000 2186 ns/op 344 B/op 8 allocs/op
+BenchmarkTigerTonic_GPlus2Params 200000 9060 ns/op 1488 B/op 24 allocs/op
+BenchmarkTraffic_GPlus2Params 100000 20324 ns/op 3272 B/op 31 allocs/op
+BenchmarkVulcan_GPlus2Params 1000000 2039 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_GPlusAll 300000 6603 ns/op 640 B/op 11 allocs/op
+BenchmarkBear_GPlusAll 100000 22363 ns/op 5488 B/op 61 allocs/op
+BenchmarkBeego_GPlusAll 50000 38757 ns/op 4784 B/op 52 allocs/op
+BenchmarkBone_GPlusAll 20000 54916 ns/op 10336 B/op 98 allocs/op
+BenchmarkDenco_GPlusAll 300000 4959 ns/op 672 B/op 11 allocs/op
+BenchmarkEcho_GPlusAll 200000 6558 ns/op 416 B/op 13 allocs/op
+BenchmarkGin_GPlusAll 500000 2757 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_GPlusAll 50000 34615 ns/op 8040 B/op 103 allocs/op
+BenchmarkGoji_GPlusAll 100000 16002 ns/op 3696 B/op 22 allocs/op
+BenchmarkGojiv2_GPlusAll 50000 35060 ns/op 12624 B/op 115 allocs/op
+BenchmarkGoJsonRest_GPlusAll 50000 41479 ns/op 8117 B/op 170 allocs/op
+BenchmarkGoRestful_GPlusAll 10000 131653 ns/op 32024 B/op 275 allocs/op
+BenchmarkGorillaMux_GPlusAll 10000 101380 ns/op 13296 B/op 142 allocs/op
+BenchmarkHttpRouter_GPlusAll 500000 3711 ns/op 640 B/op 11 allocs/op
+BenchmarkHttpTreeMux_GPlusAll 100000 14438 ns/op 4032 B/op 38 allocs/op
+BenchmarkKocha_GPlusAll 200000 8039 ns/op 976 B/op 43 allocs/op
+BenchmarkLARS_GPlusAll 500000 2630 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_GPlusAll 30000 51123 ns/op 13152 B/op 128 allocs/op
+BenchmarkMartini_GPlusAll 10000 176157 ns/op 14016 B/op 145 allocs/op
+BenchmarkPat_GPlusAll 20000 69911 ns/op 16576 B/op 298 allocs/op
+BenchmarkPossum_GPlusAll 100000 20716 ns/op 5408 B/op 39 allocs/op
+BenchmarkR2router_GPlusAll 100000 17463 ns/op 5040 B/op 63 allocs/op
+BenchmarkRivet_GPlusAll 300000 5142 ns/op 768 B/op 11 allocs/op
+BenchmarkTango_GPlusAll 50000 27321 ns/op 3656 B/op 104 allocs/op
+BenchmarkTigerTonic_GPlusAll 20000 77597 ns/op 14512 B/op 288 allocs/op
+BenchmarkTraffic_GPlusAll 10000 151406 ns/op 37360 B/op 392 allocs/op
+BenchmarkVulcan_GPlusAll 100000 18555 ns/op 1274 B/op 39 allocs/op
+```
+
+## Parse.com
+
+```
+BenchmarkGin_ParseStatic 10000000 133 ns/op 0 B/op 0 allocs/op
+
+BenchmarkAce_ParseStatic 5000000 241 ns/op 0 B/op 0 allocs/op
+BenchmarkBear_ParseStatic 2000000 728 ns/op 120 B/op 3 allocs/op
+BenchmarkBeego_ParseStatic 1000000 2623 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_ParseStatic 1000000 1285 ns/op 144 B/op 3 allocs/op
+BenchmarkDenco_ParseStatic 30000000 57.8 ns/op 0 B/op 0 allocs/op
+BenchmarkEcho_ParseStatic 5000000 342 ns/op 32 B/op 1 allocs/op
+BenchmarkGocraftWeb_ParseStatic 1000000 1478 ns/op 296 B/op 5 allocs/op
+BenchmarkGoji_ParseStatic 3000000 415 ns/op 0 B/op 0 allocs/op
+BenchmarkGojiv2_ParseStatic 1000000 2087 ns/op 928 B/op 7 allocs/op
+BenchmarkGoJsonRest_ParseStatic 1000000 1712 ns/op 329 B/op 11 allocs/op
+BenchmarkGoRestful_ParseStatic 200000 11072 ns/op 3224 B/op 22 allocs/op
+BenchmarkGorillaMux_ParseStatic 500000 4129 ns/op 752 B/op 11 allocs/op
+BenchmarkHttpRouter_ParseStatic 30000000 52.4 ns/op 0 B/op 0 allocs/op
+BenchmarkHttpTreeMux_ParseStatic 20000000 109 ns/op 0 B/op 0 allocs/op
+BenchmarkKocha_ParseStatic 20000000 81.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLARS_ParseStatic 10000000 150 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_ParseStatic 1000000 3288 ns/op 768 B/op 9 allocs/op
+BenchmarkMartini_ParseStatic 200000 9110 ns/op 768 B/op 9 allocs/op
+BenchmarkPat_ParseStatic 1000000 1135 ns/op 240 B/op 5 allocs/op
+BenchmarkPossum_ParseStatic 1000000 1557 ns/op 416 B/op 3 allocs/op
+BenchmarkR2router_ParseStatic 2000000 730 ns/op 144 B/op 4 allocs/op
+BenchmarkRivet_ParseStatic 10000000 121 ns/op 0 B/op 0 allocs/op
+BenchmarkTango_ParseStatic 1000000 1688 ns/op 248 B/op 8 allocs/op
+BenchmarkTigerTonic_ParseStatic 3000000 427 ns/op 48 B/op 1 allocs/op
+BenchmarkTraffic_ParseStatic 500000 5962 ns/op 1816 B/op 20 allocs/op
+BenchmarkVulcan_ParseStatic 2000000 969 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_ParseParam 3000000 497 ns/op 64 B/op 1 allocs/op
+BenchmarkBear_ParseParam 1000000 1473 ns/op 467 B/op 5 allocs/op
+BenchmarkBeego_ParseParam 1000000 2384 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_ParseParam 1000000 2513 ns/op 768 B/op 6 allocs/op
+BenchmarkDenco_ParseParam 5000000 364 ns/op 64 B/op 1 allocs/op
+BenchmarkEcho_ParseParam 5000000 418 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_ParseParam 10000000 163 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_ParseParam 1000000 2361 ns/op 664 B/op 8 allocs/op
+BenchmarkGoji_ParseParam 1000000 1590 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_ParseParam 1000000 2851 ns/op 976 B/op 9 allocs/op
+BenchmarkGoJsonRest_ParseParam 1000000 2965 ns/op 649 B/op 13 allocs/op
+BenchmarkGoRestful_ParseParam 200000 12207 ns/op 3544 B/op 23 allocs/op
+BenchmarkGorillaMux_ParseParam 500000 5187 ns/op 1088 B/op 12 allocs/op
+BenchmarkHttpRouter_ParseParam 5000000 275 ns/op 64 B/op 1 allocs/op
+BenchmarkHttpTreeMux_ParseParam 1000000 1108 ns/op 352 B/op 3 allocs/op
+BenchmarkKocha_ParseParam 3000000 495 ns/op 56 B/op 3 allocs/op
+BenchmarkLARS_ParseParam 10000000 192 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_ParseParam 500000 4103 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_ParseParam 200000 9878 ns/op 1072 B/op 10 allocs/op
+BenchmarkPat_ParseParam 500000 3657 ns/op 1120 B/op 17 allocs/op
+BenchmarkPossum_ParseParam 1000000 2084 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_ParseParam 1000000 1251 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_ParseParam 5000000 335 ns/op 48 B/op 1 allocs/op
+BenchmarkTango_ParseParam 1000000 1854 ns/op 280 B/op 8 allocs/op
+BenchmarkTigerTonic_ParseParam 500000 4582 ns/op 1008 B/op 17 allocs/op
+BenchmarkTraffic_ParseParam 200000 8125 ns/op 2248 B/op 23 allocs/op
+BenchmarkVulcan_ParseParam 1000000 1148 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_Parse2Params 3000000 539 ns/op 64 B/op 1 allocs/op
+BenchmarkBear_Parse2Params 1000000 1778 ns/op 496 B/op 5 allocs/op
+BenchmarkBeego_Parse2Params 1000000 2519 ns/op 368 B/op 4 allocs/op
+BenchmarkBone_Parse2Params 1000000 2596 ns/op 720 B/op 5 allocs/op
+BenchmarkDenco_Parse2Params 3000000 492 ns/op 64 B/op 1 allocs/op
+BenchmarkEcho_Parse2Params 3000000 484 ns/op 32 B/op 1 allocs/op
+BenchmarkGin_Parse2Params 10000000 193 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_Parse2Params 1000000 2575 ns/op 712 B/op 9 allocs/op
+BenchmarkGoji_Parse2Params 1000000 1373 ns/op 336 B/op 2 allocs/op
+BenchmarkGojiv2_Parse2Params 500000 2416 ns/op 960 B/op 8 allocs/op
+BenchmarkGoJsonRest_Parse2Params 300000 3452 ns/op 713 B/op 14 allocs/op
+BenchmarkGoRestful_Parse2Params 100000 17719 ns/op 6008 B/op 25 allocs/op
+BenchmarkGorillaMux_Parse2Params 300000 5102 ns/op 1088 B/op 11 allocs/op
+BenchmarkHttpRouter_Parse2Params 5000000 303 ns/op 64 B/op 1 allocs/op
+BenchmarkHttpTreeMux_Parse2Params 1000000 1372 ns/op 384 B/op 4 allocs/op
+BenchmarkKocha_Parse2Params 2000000 874 ns/op 128 B/op 5 allocs/op
+BenchmarkLARS_Parse2Params 10000000 192 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_Parse2Params 500000 3871 ns/op 1056 B/op 10 allocs/op
+BenchmarkMartini_Parse2Params 200000 9954 ns/op 1152 B/op 11 allocs/op
+BenchmarkPat_Parse2Params 500000 4194 ns/op 832 B/op 17 allocs/op
+BenchmarkPossum_Parse2Params 1000000 2121 ns/op 560 B/op 6 allocs/op
+BenchmarkR2router_Parse2Params 1000000 1415 ns/op 432 B/op 5 allocs/op
+BenchmarkRivet_Parse2Params 3000000 457 ns/op 96 B/op 1 allocs/op
+BenchmarkTango_Parse2Params 1000000 1914 ns/op 312 B/op 8 allocs/op
+BenchmarkTigerTonic_Parse2Params 300000 6895 ns/op 1408 B/op 24 allocs/op
+BenchmarkTraffic_Parse2Params 200000 8317 ns/op 2040 B/op 22 allocs/op
+BenchmarkVulcan_Parse2Params 1000000 1274 ns/op 98 B/op 3 allocs/op
+BenchmarkAce_ParseAll 200000 10401 ns/op 640 B/op 16 allocs/op
+BenchmarkBear_ParseAll 50000 37743 ns/op 8928 B/op 110 allocs/op
+BenchmarkBeego_ParseAll 20000 63193 ns/op 9568 B/op 104 allocs/op
+BenchmarkBone_ParseAll 20000 61767 ns/op 14160 B/op 131 allocs/op
+BenchmarkDenco_ParseAll 300000 7036 ns/op 928 B/op 16 allocs/op
+BenchmarkEcho_ParseAll 200000 11824 ns/op 832 B/op 26 allocs/op
+BenchmarkGin_ParseAll 300000 4199 ns/op 0 B/op 0 allocs/op
+BenchmarkGocraftWeb_ParseAll 30000 51758 ns/op 13728 B/op 181 allocs/op
+BenchmarkGoji_ParseAll 50000 29614 ns/op 5376 B/op 32 allocs/op
+BenchmarkGojiv2_ParseAll 20000 68676 ns/op 24464 B/op 199 allocs/op
+BenchmarkGoJsonRest_ParseAll 20000 76135 ns/op 13866 B/op 321 allocs/op
+BenchmarkGoRestful_ParseAll 5000 389487 ns/op 110928 B/op 600 allocs/op
+BenchmarkGorillaMux_ParseAll 10000 221250 ns/op 24864 B/op 292 allocs/op
+BenchmarkHttpRouter_ParseAll 200000 6444 ns/op 640 B/op 16 allocs/op
+BenchmarkHttpTreeMux_ParseAll 50000 30702 ns/op 5728 B/op 51 allocs/op
+BenchmarkKocha_ParseAll 200000 13712 ns/op 1112 B/op 54 allocs/op
+BenchmarkLARS_ParseAll 300000 6925 ns/op 0 B/op 0 allocs/op
+BenchmarkMacaron_ParseAll 20000 96278 ns/op 24576 B/op 250 allocs/op
+BenchmarkMartini_ParseAll 5000 271352 ns/op 25072 B/op 253 allocs/op
+BenchmarkPat_ParseAll 20000 74941 ns/op 17264 B/op 343 allocs/op
+BenchmarkPossum_ParseAll 50000 39947 ns/op 10816 B/op 78 allocs/op
+BenchmarkR2router_ParseAll 50000 42479 ns/op 8352 B/op 120 allocs/op
+BenchmarkRivet_ParseAll 200000 7726 ns/op 912 B/op 16 allocs/op
+BenchmarkTango_ParseAll 30000 50014 ns/op 7168 B/op 208 allocs/op
+BenchmarkTigerTonic_ParseAll 10000 106550 ns/op 19728 B/op 379 allocs/op
+BenchmarkTraffic_ParseAll 10000 216037 ns/op 57776 B/op 642 allocs/op
+BenchmarkVulcan_ParseAll 50000 34379 ns/op 2548 B/op 78 allocs/op
```
diff --git a/Makefile b/Makefile
index 9ba475a4..5468563a 100644
--- a/Makefile
+++ b/Makefile
@@ -2,14 +2,14 @@ GOFMT ?= gofmt "-s"
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
-all: build
+all: install
install: deps
govendor sync
.PHONY: test
test:
- go test -v -covermode=count -coverprofile=coverage.out
+ sh coverage.sh
.PHONY: fmt
fmt:
diff --git a/README.md b/README.md
index 7e3df99f..0bac65b2 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,58 @@
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
[](https://godoc.org/github.com/gin-gonic/gin)
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](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.

+## Contents
+
+- [Quick start](#quick-start)
+- [Benchmarks](#benchmarks)
+- [Gin v1.stable](#gin-v1-stable)
+- [Start using it](#start-using-it)
+- [Build with jsoniter](#build-with-jsoniter)
+- [API Examples](#api-examples)
+ - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
+ - [Parameters in path](#parameters-in-path)
+ - [Querystring parameters](#querystring-parameters)
+ - [Multipart/Urlencoded Form](#multiparturlencoded-form)
+ - [Another example: query + post form](#another-example-query--post-form)
+ - [Upload files](#upload-files)
+ - [Grouping routes](#grouping-routes)
+ - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
+ - [Using middleware](#using-middleware)
+ - [How to write log file](#how-to-write-log-file)
+ - [Model binding and validation](#model-binding-and-validation)
+ - [Custom Validators](#custom-validators)
+ - [Only Bind Query String](#only-bind-query-string)
+ - [Bind Query String or Post Data](#bind-query-string-or-post-data)
+ - [Bind HTML checkboxes](#bind-html-checkboxes)
+ - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
+ - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
+ - [JSONP rendering](#jsonp)
+ - [Serving static files](#serving-static-files)
+ - [Serving data from reader](#serving-data-from-reader)
+ - [HTML rendering](#html-rendering)
+ - [Multitemplate](#multitemplate)
+ - [Redirects](#redirects)
+ - [Custom Middleware](#custom-middleware)
+ - [Using BasicAuth() middleware](#using-basicauth-middleware)
+ - [Goroutines inside a middleware](#goroutines-inside-a-middleware)
+ - [Custom HTTP configuration](#custom-http-configuration)
+ - [Support Let's Encrypt](#support-lets-encrypt)
+ - [Run multiple service using Gin](#run-multiple-service-using-gin)
+ - [Graceful restart or stop](#graceful-restart-or-stop)
+ - [Build a single binary with templates](#build-a-single-binary-with-templates)
+ - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
+ - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
+- [Testing](#testing)
+- [Users](#users--)
+
+## Quick start
+
```sh
# assume the following codes in example.go file
$ cat example.go
@@ -40,45 +87,44 @@ $ go run example.go
## Benchmarks
-Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
+Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
[See all benchmarks](/BENCHMARKS.md)
+Benchmark name | (1) | (2) | (3) | (4)
+--------------------------------------------|-----------:|------------:|-----------:|---------:
+**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0**
+BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167
+BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943
+BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812
+BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453
+BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167
+BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203
+BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686
+BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334
+BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712
+BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737
+BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519
+BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272
+BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167
+BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671
+BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843
+BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0
+BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000
+BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325
+BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435
+BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609
+BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979
+BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167
+BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618
+BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374
+BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
+BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
-Benchmark name | (1) | (2) | (3) | (4)
---------------------------------|----------:|----------:|----------:|------:
-BenchmarkAce_GithubAll | 10000 | 109482 | 13792 | 167
-BenchmarkBear_GithubAll | 10000 | 287490 | 79952 | 943
-BenchmarkBeego_GithubAll | 3000 | 562184 | 146272 | 2092
-BenchmarkBone_GithubAll | 500 | 2578716 | 648016 | 8119
-BenchmarkDenco_GithubAll | 20000 | 94955 | 20224 | 167
-BenchmarkEcho_GithubAll | 30000 | 58705 | 0 | 0
-**BenchmarkGin_GithubAll** | **30000** | **50991** | **0** | **0**
-BenchmarkGocraftWeb_GithubAll | 5000 | 449648 | 133280 | 1889
-BenchmarkGoji_GithubAll | 2000 | 689748 | 56113 | 334
-BenchmarkGoJsonRest_GithubAll | 5000 | 537769 | 135995 | 2940
-BenchmarkGoRestful_GithubAll | 100 | 18410628 | 797236 | 7725
-BenchmarkGorillaMux_GithubAll | 200 | 8036360 | 153137 | 1791
-BenchmarkHttpRouter_GithubAll | 20000 | 63506 | 13792 | 167
-BenchmarkHttpTreeMux_GithubAll | 10000 | 165927 | 56112 | 334
-BenchmarkKocha_GithubAll | 10000 | 171362 | 23304 | 843
-BenchmarkMacaron_GithubAll | 2000 | 817008 | 224960 | 2315
-BenchmarkMartini_GithubAll | 100 | 12609209 | 237952 | 2686
-BenchmarkPat_GithubAll | 300 | 4830398 | 1504101 | 32222
-BenchmarkPossum_GithubAll | 10000 | 301716 | 97440 | 812
-BenchmarkR2router_GithubAll | 10000 | 270691 | 77328 | 1182
-BenchmarkRevel_GithubAll | 1000 | 1491919 | 345553 | 5918
-BenchmarkRivet_GithubAll | 10000 | 283860 | 84272 | 1079
-BenchmarkTango_GithubAll | 5000 | 473821 | 87078 | 2470
-BenchmarkTigerTonic_GithubAll | 2000 | 1120131 | 241088 | 6052
-BenchmarkTraffic_GithubAll | 200 | 8708979 | 2664762 | 22390
-BenchmarkVulcan_GithubAll | 5000 | 353392 | 19894 | 609
-BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
-
-(1): Total Repetitions
-(2): Single Repetition Duration (ns/op)
-(3): Heap Memory (B/op)
-(4): Average Allocations per Repetition (allocs/op)
+- (1): Total Repetitions achieved in constant time, higher means more confident result
+- (2): Single Repetition Duration (ns/op), lower is better
+- (3): Heap Memory (B/op), lower is better
+- (4): Average Allocations per Repetition (allocs/op), lower is better
## Gin v1. stable
@@ -88,7 +134,6 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
- [x] Battle tested
- [x] API frozen, new releases will not break your code.
-
## Start using it
1. Download and install it:
@@ -119,7 +164,7 @@ $ go get github.com/kardianos/govendor
2. Create your project folder and `cd` inside
```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
@@ -141,6 +186,14 @@ $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/mai
$ 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
### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
@@ -271,14 +324,16 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail
```go
func main() {
router := gin.Default()
+ // Set a lower memory limit for multipart forms (default is 32 MiB)
+ // router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// single file
file, _ := c.FormFile("file")
log.Println(file.Filename)
-
+
// Upload the file to specific dst.
- // c.SaveUploadedFile(file, dst)
-
+ // c.SaveUploadedFile(file, dst)
+
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
@@ -300,6 +355,8 @@ See the detail [example code](examples/upload-file/multiple).
```go
func main() {
router := gin.Default()
+ // Set a lower memory limit for multipart forms (default is 32 MiB)
+ // router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
@@ -307,9 +364,9 @@ func main() {
for _, file := range files {
log.Println(file.Filename)
-
+
// Upload the file to specific dst.
- // c.SaveUploadedFile(file, dst)
+ // c.SaveUploadedFile(file, dst)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
@@ -363,6 +420,7 @@ r := gin.New()
instead of
```go
+// Default With the Logger and Recovery middleware already attached
r := gin.Default()
```
@@ -374,7 +432,11 @@ func main() {
r := gin.New()
// Global middleware
+ // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
+ // By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
+
+ // Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire.
@@ -402,6 +464,28 @@ 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
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).
@@ -410,9 +494,17 @@ Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/valid
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
// Binding from JSON
@@ -427,12 +519,14 @@ func main() {
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
- if c.BindJSON(&json) == nil {
+ if err := c.ShouldBindJSON(&json); err == nil {
if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
+ } else {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
@@ -440,12 +534,14 @@ func main() {
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// 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" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
+ } else {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
@@ -454,7 +550,137 @@ 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"}
+```
+
+**Skip validate**
+
+When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
+
+### Custom Validators
+
+It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
+
+[embedmd]:# (examples/custom-validation/server.go go)
+```go
+package main
+
+import (
+ "net/http"
+ "reflect"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+ "gopkg.in/go-playground/validator.v8"
+)
+
+type Booking struct {
+ CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
+ CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
+}
+
+func bookableDate(
+ v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
+ field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
+) bool {
+ if date, ok := field.Interface().(time.Time); ok {
+ today := time.Now()
+ if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
+ return false
+ }
+ }
+ return true
+}
+
+func main() {
+ route := gin.Default()
+
+ if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
+ v.RegisterValidation("bookabledate", bookableDate)
+ }
+
+ route.GET("/bookable", getBookable)
+ route.Run(":8085")
+}
+
+func getBookable(c *gin.Context) {
+ var b Booking
+ if err := c.ShouldBindWith(&b, binding.Query); err == nil {
+ c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
+ } else {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ }
+}
+```
+
+```console
+$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
+{"message":"Booking dates are valid!"}
+
+$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
+{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
+```
+
+[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way.
+See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
+
+### Only Bind Query String
+
+`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/gin-gonic/gin"
+)
+
+type Person struct {
+ Name string `form:"name"`
+ Address string `form:"address"`
+}
+
+func main() {
+ route := gin.Default()
+ route.Any("/testing", startPage)
+ route.Run(":8085")
+}
+
+func startPage(c *gin.Context) {
+ var person Person
+ if c.ShouldBindQuery(&person) == nil {
+ log.Println("====== Only Bind By Query String ======")
+ log.Println(person.Name)
+ log.Println(person.Address)
+ }
+ c.String(200, "Success")
+}
+
+```
+
+### Bind Query String or Post Data
See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
@@ -463,10 +689,12 @@ package main
import "log"
import "github.com/gin-gonic/gin"
+import "time"
type Person struct {
- Name string `form:"name"`
- Address string `form:"address"`
+ Name string `form:"name"`
+ Address string `form:"address"`
+ Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
@@ -480,15 +708,21 @@ func startPage(c *gin.Context) {
// If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
- if c.Bind(&person) == nil {
+ if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
+ log.Println(person.Birthday)
}
c.String(200, "Success")
}
```
+Test it with:
+```sh
+$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
+```
+
### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
@@ -506,7 +740,7 @@ type myForm struct {
func formHandler(c *gin.Context) {
var fakeForm myForm
- c.Bind(&fakeForm)
+ c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors})
}
@@ -553,11 +787,11 @@ func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration:
- // c.MustBindWith(&form, binding.Form)
- // or you can simply use autobinding with Bind method:
+ // c.ShouldBindWith(&form, binding.Form)
+ // or you can simply use autobinding with ShouldBind method:
var form LoginForm
// in this case proper binding will be automatically selected
- if c.Bind(&form) == nil {
+ if c.ShouldBind(&form) == nil {
if form.User == "user" && form.Password == "password" {
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
@@ -634,7 +868,29 @@ func main() {
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
-```
+```
+#### JSONP
+
+Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
+
+```go
+func main() {
+ r := gin.Default()
+
+ r.GET("/JSONP?callback=x", func(c *gin.Context) {
+ data := map[string]interface{}{
+ "foo": "bar",
+ }
+
+ //callback is x
+ // Will output : x({\"foo\":\"bar\"})
+ c.JSONP(http.StatusOK, data)
+ })
+
+ // Listen and serve on 0.0.0.0:8080
+ r.Run(":8080")
+}
+```
### Serving static files
@@ -650,6 +906,32 @@ func main() {
}
```
+### Serving data from reader
+
+```go
+func main() {
+ router := gin.Default()
+ router.GET("/someDataFromReader", func(c *gin.Context) {
+ response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
+ if err != nil || response.StatusCode != http.StatusOK {
+ c.Status(http.StatusServiceUnavailable)
+ return
+ }
+
+ reader := response.Body
+ contentLength := response.ContentLength
+ contentType := response.Header.Get("Content-Type")
+
+ extraHeaders := map[string]string{
+ "Content-Disposition": `attachment; filename="gopher.png"`,
+ }
+
+ c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
+ })
+ router.Run(":8080")
+}
+```
+
### HTML rendering
Using LoadHTMLGlob() or LoadHTMLFiles()
@@ -745,35 +1027,46 @@ You may use custom delims
r := gin.Default()
r.Delims("{[{", "}]}")
r.LoadHTMLGlob("/path/to/templates"))
-```
+```
#### Custom Template Funcs
+See the detail [example code](examples/template).
+
main.go
```go
- ...
-
- func formatAsDate(t time.Time) string {
- year, month, day := t.Date()
- return fmt.Sprintf("%d/%02d/%02d", year, month, day)
- }
-
- ...
-
- router.SetFuncMap(template.FuncMap{
- "formatAsDate": formatAsDate,
- })
-
- ...
-
- router.GET("/raw", func(c *Context) {
- c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
- "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
- })
- })
-
- ...
+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")
+}
+
```
raw.tmpl
@@ -793,14 +1086,26 @@ Gin allow by default use only one html.Template. Check [a multitemplate render](
### Redirects
-Issuing a HTTP redirect is easy:
+Issuing a HTTP redirect is easy. Both internal and external locations are supported.
```go
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
```
-Both internal and external locations are supported.
+
+
+Issuing a Router redirect, use `HandleContext` like below.
+
+``` go
+r.GET("/test", func(c *gin.Context) {
+ c.Request.URL.Path = "/test2"
+ r.HandleContext(c)
+})
+r.GET("/test2", func(c *gin.Context) {
+ c.JSON(200, gin.H{"hello": "world"})
+})
+```
### Custom Middleware
@@ -884,7 +1189,7 @@ func main() {
### 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
func main() {
@@ -946,7 +1251,7 @@ func main() {
example for 1-line LetsEncrypt HTTPS servers.
-[embedmd]:# (examples/auto-tls/example1.go go)
+[embedmd]:# (examples/auto-tls/example1/main.go go)
```go
package main
@@ -971,7 +1276,7 @@ func main() {
example for custom autocert manager.
-[embedmd]:# (examples/auto-tls/example2.go go)
+[embedmd]:# (examples/auto-tls/example2/main.go go)
```go
package main
@@ -1001,6 +1306,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
Do you want to graceful restart or stop your web server?
@@ -1054,8 +1441,8 @@ func main() {
go func() {
// service connections
- if err := srv.ListenAndServe(); err != nil {
- log.Printf("listen: %s\n", err)
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Fatalf("listen: %s\n", err)
}
}()
@@ -1071,7 +1458,247 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
- log.Println("Server exist")
+ log.Println("Server exiting")
+}
+```
+
+### Build a single binary with templates
+
+You can build a server into a single binary containing templates by using [go-assets][].
+
+[go-assets]: https://github.com/jessevdk/go-assets
+
+```go
+func main() {
+ r := gin.New()
+
+ t, err := loadTemplate()
+ if err != nil {
+ panic(err)
+ }
+ r.SetHTMLTemplate(t)
+
+ r.GET("/", func(c *gin.Context) {
+ c.HTML(http.StatusOK, "/html/index.tmpl",nil)
+ })
+ r.Run(":8080")
+}
+
+// loadTemplate loads templates embedded by go-assets-builder
+func loadTemplate() (*template.Template, error) {
+ t := template.New("")
+ for name, file := range Assets.Files {
+ if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
+ continue
+ }
+ h, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+ t, err = t.New(name).Parse(string(h))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return t, nil
+}
+```
+
+See a complete example in the `examples/assets-in-binary` directory.
+
+### Bind form-data request with custom struct
+
+The follow example using custom struct:
+
+```go
+type StructA struct {
+ FieldA string `form:"field_a"`
+}
+
+type StructB struct {
+ NestedStruct StructA
+ FieldB string `form:"field_b"`
+}
+
+type StructC struct {
+ NestedStructPointer *StructA
+ FieldC string `form:"field_c"`
+}
+
+type StructD struct {
+ NestedAnonyStruct struct {
+ FieldX string `form:"field_x"`
+ }
+ FieldD string `form:"field_d"`
+}
+
+func GetDataB(c *gin.Context) {
+ var b StructB
+ c.Bind(&b)
+ c.JSON(200, gin.H{
+ "a": b.NestedStruct,
+ "b": b.FieldB,
+ })
+}
+
+func GetDataC(c *gin.Context) {
+ var b StructC
+ c.Bind(&b)
+ c.JSON(200, gin.H{
+ "a": b.NestedStructPointer,
+ "c": b.FieldC,
+ })
+}
+
+func GetDataD(c *gin.Context) {
+ var b StructD
+ c.Bind(&b)
+ c.JSON(200, gin.H{
+ "x": b.NestedAnonyStruct,
+ "d": b.FieldD,
+ })
+}
+
+func main() {
+ r := gin.Default()
+ r.GET("/getb", GetDataB)
+ r.GET("/getc", GetDataC)
+ r.GET("/getd", GetDataD)
+
+ r.Run()
+}
+```
+
+Using the command `curl` command result:
+
+```
+$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
+{"a":{"FieldA":"hello"},"b":"world"}
+$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
+{"a":{"FieldA":"hello"},"c":"world"}
+$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
+{"d":"world","x":{"FieldX":"hello"}}
+```
+
+**NOTE**: NOT support the follow style struct:
+
+```go
+type StructX struct {
+ X struct {} `form:"name_x"` // HERE have form
+}
+
+type StructY struct {
+ Y StructX `form:"name_y"` // HERE hava form
+}
+
+type StructZ struct {
+ Z *StructZ `form:"name_z"` // HERE hava form
+}
+```
+
+In a word, only support nested custom struct which have no `form` now.
+
+### Try to bind body into different structs
+
+The normal methods for binding request body consumes `c.Request.Body` and they
+cannot be called multiple times.
+
+```go
+type formA struct {
+ Foo string `json:"foo" xml:"foo" binding:"required"`
+}
+
+type formB struct {
+ Bar string `json:"bar" xml:"bar" binding:"required"`
+}
+
+func SomeHandler(c *gin.Context) {
+ objA := formA{}
+ objB := formB{}
+ // This c.ShouldBind consumes c.Request.Body and it cannot be reused.
+ if errA := c.ShouldBind(&objA); errA == nil {
+ c.String(http.StatusOK, `the body should be formA`)
+ // Always an error is occurred by this because c.Request.Body is EOF now.
+ } else if errB := c.ShouldBind(&objB); errB == nil {
+ c.String(http.StatusOK, `the body should be formB`)
+ } else {
+ ...
+ }
+}
+```
+
+For this, you can use `c.ShouldBindBodyWith`.
+
+```go
+func SomeHandler(c *gin.Context) {
+ objA := formA{}
+ objB := formB{}
+ // This reads c.Request.Body and stores the result into the context.
+ if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
+ c.String(http.StatusOK, `the body should be formA`)
+ // At this time, it reuses body stored in the context.
+ } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
+ c.String(http.StatusOK, `the body should be formB JSON`)
+ // And it can accepts other formats
+ } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
+ c.String(http.StatusOK, `the body should be formB XML`)
+ } else {
+ ...
+ }
+}
+```
+
+* `c.ShouldBindBodyWith` stores body into the context before binding. This has
+a slight impact to performance, so you should not use this method if you are
+enough to call binding at once.
+* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
+`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
+can be called by `c.ShouldBind()` multiple times without any damage to
+performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
+
+## 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())
}
```
diff --git a/auth.go b/auth.go
index 75a7c892..c2143091 100644
--- a/auth.go
+++ b/auth.go
@@ -10,26 +10,26 @@ import (
"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"
-// 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 authPair struct {
- Value string
- User string
+ value string
+ user string
}
type authPairs []authPair
func (a authPairs) searchCredential(authValue string) (string, bool) {
- if len(authValue) == 0 {
+ if authValue == "" {
return "", false
}
for _, pair := range a {
- if pair.Value == authValue {
- return pair.User, true
+ if pair.value == authValue {
+ return pair.user, true
}
}
return "", false
@@ -47,7 +47,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
pairs := processAccounts(accounts)
return func(c *Context) {
// 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 {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
@@ -56,9 +56,8 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
}
// 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.MustGet(gin.AuthUserKey).
c.Set(AuthUserKey, user)
- return
}
}
@@ -72,11 +71,11 @@ func processAccounts(accounts Accounts) authPairs {
assert1(len(accounts) > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, len(accounts))
for user, password := range accounts {
- assert1(len(user) > 0, "User can not be empty")
+ assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
- Value: value,
- User: user,
+ value: value,
+ user: user,
})
}
return pairs
@@ -91,6 +90,6 @@ func secureCompare(given, actual string) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
}
- // Securely compare actual to itself to keep constant time, but always return false
+ // Securely compare actual to itself to keep constant time, but always return false.
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
}
diff --git a/auth_test.go b/auth_test.go
index b22d9ced..dc8523b0 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -22,16 +22,16 @@ func TestBasicAuth(t *testing.T) {
assert.Len(t, pairs, 3)
assert.Contains(t, pairs, authPair{
- User: "bar",
- Value: "Basic YmFyOmZvbw==",
+ user: "bar",
+ value: "Basic YmFyOmZvbw==",
})
assert.Contains(t, pairs, authPair{
- User: "foo",
- Value: "Basic Zm9vOmJhcg==",
+ user: "foo",
+ value: "Basic Zm9vOmJhcg==",
})
assert.Contains(t, pairs, authPair{
- User: "admin",
- Value: "Basic YWRtaW46cGFzc3dvcmQ=",
+ user: "admin",
+ value: "Basic YWRtaW46cGFzc3dvcmQ=",
})
}
@@ -53,15 +53,15 @@ func TestBasicAuthSearchCredential(t *testing.T) {
})
user, found := pairs.searchCredential(authorizationHeader("admin", "password"))
- assert.Equal(t, user, "admin")
+ assert.Equal(t, "admin", user)
assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("foo", "bar"))
- assert.Equal(t, user, "foo")
+ assert.Equal(t, "foo", user)
assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("bar", "foo"))
- assert.Equal(t, user, "bar")
+ assert.Equal(t, "bar", user)
assert.True(t, found)
user, found = pairs.searchCredential(authorizationHeader("admins", "password"))
@@ -78,7 +78,7 @@ func TestBasicAuthSearchCredential(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) {
@@ -101,8 +101,8 @@ func TestBasicAuthSucceed(t *testing.T) {
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
router.ServeHTTP(w, req)
- assert.Equal(t, w.Code, 200)
- assert.Equal(t, w.Body.String(), "admin")
+ assert.Equal(t, 200, w.Code)
+ assert.Equal(t, "admin", w.Body.String())
}
func TestBasicAuth401(t *testing.T) {
@@ -121,8 +121,8 @@ func TestBasicAuth401(t *testing.T) {
router.ServeHTTP(w, req)
assert.False(t, called)
- assert.Equal(t, w.Code, 401)
- assert.Equal(t, w.HeaderMap.Get("WWW-Authenticate"), "Basic realm=\"Authorization Required\"")
+ assert.Equal(t, 401, w.Code)
+ assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate"))
}
func TestBasicAuth401WithCustomRealm(t *testing.T) {
@@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
router.ServeHTTP(w, req)
assert.False(t, called)
- assert.Equal(t, w.Code, 401)
- assert.Equal(t, w.HeaderMap.Get("WWW-Authenticate"), "Basic realm=\"My Custom \\\"Realm\\\"\"")
+ assert.Equal(t, 401, w.Code)
+ assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate"))
}
diff --git a/benchmarks_test.go b/benchmarks_test.go
index a2c62ba3..e7970034 100644
--- a/benchmarks_test.go
+++ b/benchmarks_test.go
@@ -59,8 +59,6 @@ func BenchmarkOneRouteJSON(B *testing.B) {
runRequest(B, router, "GET", "/json")
}
-var htmlContentType = []string{"text/html; charset=utf-8"}
-
func BenchmarkOneRouteHTML(B *testing.B) {
router := New()
t := template.Must(template.New("index").Parse(`
diff --git a/binding/binding.go b/binding/binding.go
index 1dbf2460..1a984777 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -4,7 +4,9 @@
package binding
-import "net/http"
+import (
+ "net/http"
+)
const (
MIMEJSON = "application/json"
@@ -19,11 +21,25 @@ const (
MIMEMSGPACK2 = "application/msgpack"
)
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+ Binding
+ BindBody([]byte, interface{}) error
+}
+
+// StructValidator is the minimal interface which needs to be implemented in
+// order for it to be used as the validator engine for ensuring the correctness
+// of the reqest. Gin provides a default implementation for this using
+// https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned.
@@ -31,20 +47,32 @@ type StructValidator interface {
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned.
ValidateStruct(interface{}) error
+
+ // Engine returns the underlying validator engine which powers the
+ // StructValidator implementation.
+ Engine() interface{}
}
+// Validator is the default validator which implements the StructValidator
+// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
+// under the hood.
var Validator StructValidator = &defaultValidator{}
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
+ Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
)
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
func Default(method, contentType string) Binding {
if method == "GET" {
return Form
diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go
new file mode 100644
index 00000000..c41d9f86
--- /dev/null
+++ b/binding/binding_body_test.go
@@ -0,0 +1,67 @@
+package binding
+
+import (
+ "bytes"
+ "io/ioutil"
+ "testing"
+
+ "github.com/gin-gonic/gin/binding/example"
+ "github.com/golang/protobuf/proto"
+ "github.com/stretchr/testify/assert"
+ "github.com/ugorji/go/codec"
+)
+
+func TestBindingBody(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ binding BindingBody
+ body string
+ want string
+ }{
+ {
+ name: "JSON bidning",
+ binding: JSON,
+ body: `{"foo":"FOO"}`,
+ },
+ {
+ name: "XML bidning",
+ binding: XML,
+ body: `
+
Can you see this? → {{.Bar}}
\n\n" +var _Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2 = "\n\nHello, {{.Foo}}
\n\n" + +// Assets returns go-assets FileSystem +var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{ + "/": { + Path: "/", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365738, 1524365738517125470), + Data: nil, + }, "/html": { + Path: "/html", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365491, 1524365491289799093), + Data: nil, + }, "/html/bar.tmpl": { + Path: "/html/bar.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289611557), + Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003), + }, "/html/index.tmpl": { + Path: "/html/index.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289995821), + Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2), + }}, "") diff --git a/examples/assets-in-binary/html/bar.tmpl b/examples/assets-in-binary/html/bar.tmpl new file mode 100644 index 00000000..c8e1c0ff --- /dev/null +++ b/examples/assets-in-binary/html/bar.tmpl @@ -0,0 +1,4 @@ + + +Can you see this? → {{.Bar}}
+ diff --git a/examples/assets-in-binary/html/index.tmpl b/examples/assets-in-binary/html/index.tmpl new file mode 100644 index 00000000..6904fd58 --- /dev/null +++ b/examples/assets-in-binary/html/index.tmpl @@ -0,0 +1,4 @@ + + +Hello, {{.Foo}}
+ diff --git a/examples/assets-in-binary/main.go b/examples/assets-in-binary/main.go new file mode 100644 index 00000000..27bc3b17 --- /dev/null +++ b/examples/assets-in-binary/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "html/template" + "io/ioutil" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.New() + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl", gin.H{ + "Foo": "World", + }) + }) + r.GET("/bar", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/bar.tmpl", gin.H{ + "Bar": "World", + }) + }) + r.Run(":8080") +} + +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} diff --git a/examples/auto-tls/example1.go b/examples/auto-tls/example1/main.go similarity index 100% rename from examples/auto-tls/example1.go rename to examples/auto-tls/example1/main.go diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2/main.go similarity index 100% rename from examples/auto-tls/example2.go rename to examples/auto-tls/example2/main.go diff --git a/examples/basic/main.go b/examples/basic/main.go index 984c06ab..473c6a09 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -6,7 +6,7 @@ import ( var DB = make(map[string]string) -func main() { +func setupRouter() *gin.Engine { // Disable Console Color // gin.DisableConsoleColor() r := gin.Default() @@ -53,6 +53,11 @@ func main() { } }) + return r +} + +func main() { + r := setupRouter() // Listen and Server in 0.0.0.0:8080 r.Run(":8080") } diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go new file mode 100644 index 00000000..61203d66 --- /dev/null +++ b/examples/basic/main_test.go @@ -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()) +} diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go new file mode 100644 index 00000000..dea0c302 --- /dev/null +++ b/examples/custom-validation/server.go @@ -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()}) + } +} diff --git a/examples/graceful-shutdown/close/server.go b/examples/graceful-shutdown/close/server.go index 54778393..9c4e90fa 100644 --- a/examples/graceful-shutdown/close/server.go +++ b/examples/graceful-shutdown/close/server.go @@ -41,5 +41,5 @@ func main() { } } - log.Println("Server exist") + log.Println("Server exiting") } diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 060de081..af4f2146 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -27,8 +27,8 @@ func main() { go func() { // service connections - if err := srv.ListenAndServe(); err != nil { - log.Printf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) } }() @@ -44,5 +44,5 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } - log.Println("Server exist") + log.Println("Server exiting") } diff --git a/examples/multiple-service/main.go b/examples/multiple-service/main.go new file mode 100644 index 00000000..ceddaa2e --- /dev/null +++ b/examples/multiple-service/main.go @@ -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) + } +} diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 4bca3ae4..4afedcb5 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -29,8 +29,8 @@ func statsWorker() { "timestamp": uint64(time.Now().Unix()), "HeapInuse": stats.HeapInuse, "StackInuse": stats.StackInuse, - "Mallocs": (stats.Mallocs - lastMallocs), - "Frees": (stats.Frees - lastFrees), + "Mallocs": stats.Mallocs - lastMallocs, + "Frees": stats.Frees - lastFrees, "Inbound": uint64(messages.Get("inbound")), "Outbound": uint64(messages.Get("outbound")), "Connected": connectedUsers(), diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md new file mode 100644 index 00000000..1bd57f03 --- /dev/null +++ b/examples/struct-lvl-validations/README.md @@ -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 diff --git a/examples/struct-lvl-validations/server.go b/examples/struct-lvl-validations/server.go new file mode 100644 index 00000000..be807b78 --- /dev/null +++ b/examples/struct-lvl-validations/server.go @@ -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(), + }) + } +} diff --git a/examples/template/main.go b/examples/template/main.go new file mode 100644 index 00000000..f9e611df --- /dev/null +++ b/examples/template/main.go @@ -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") +} diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 4bb4cdcb..a55325ed 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -9,6 +9,8 @@ import ( func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 372a2994..5d438651 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -9,6 +9,8 @@ import ( func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") diff --git a/fixtures/testdata/cert.pem b/fixtures/testdata/cert.pem new file mode 100644 index 00000000..c1d3d632 --- /dev/null +++ b/fixtures/testdata/cert.pem @@ -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----- diff --git a/fixtures/testdata/key.pem b/fixtures/testdata/key.pem new file mode 100644 index 00000000..c2a0181f --- /dev/null +++ b/fixtures/testdata/key.pem @@ -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----- diff --git a/fs.go b/fs.go index 8570a9a9..7a6738a6 100644 --- a/fs.go +++ b/fs.go @@ -29,7 +29,7 @@ func Dir(root string, listDirectory bool) http.FileSystem { return &onlyfilesFS{fs} } -// Open conforms to http.Filesystem +// Open conforms to http.Filesystem. func (fs onlyfilesFS) Open(name string) (http.File, error) { f, err := fs.fs.Open(name) if err != nil { @@ -38,7 +38,7 @@ func (fs onlyfilesFS) Open(name string) (http.File, error) { 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) { // this disables directory listing return nil, nil diff --git a/gin.go b/gin.go index fc9214dc..b91fc2fa 100644 --- a/gin.go +++ b/gin.go @@ -14,12 +14,17 @@ import ( "github.com/gin-gonic/gin/render" ) -// Version is Framework's version -const Version = "v1.2" +const ( + // Version is Framework's version. + Version = "v1.2" + defaultMultipartMemory = 32 << 20 // 32 MB +) -var default404Body = []byte("404 page not found") -var default405Body = []byte("405 method not allowed") -var defaultAppEngine bool +var ( + default404Body = []byte("404 page not found") + default405Body = []byte("405 method not allowed") + defaultAppEngine bool +) type HandlerFunc func(*Context) type HandlersChain []HandlerFunc @@ -44,16 +49,6 @@ type RoutesInfo []RouteInfo // Create an instance of Engine, by using New() or Default() type Engine struct { 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 // 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. UseRawPath bool + // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool + + // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm + // method call. + MaxMultipartMemory int64 + + delims render.Delims + secureJsonPrefix string + HTMLRender render.HTMLRender + FuncMap template.FuncMap + allNoRoute HandlersChain + allNoMethod HandlersChain + noRoute HandlersChain + noMethod HandlersChain + pool sync.Pool + trees methodTrees } var _ IRouter = &Engine{} @@ -120,8 +131,9 @@ func New() *Engine { AppEngine: defaultAppEngine, UseRawPath: false, UnescapePathValues: true, + MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), - delims: render.Delims{"{{", "}}"}, + delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine @@ -133,6 +145,7 @@ func New() *Engine { // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { + debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine @@ -143,36 +156,45 @@ func (engine *Engine) allocateContext() *Context { } func (engine *Engine) Delims(left, right string) *Engine { - engine.delims = render.Delims{left, right} + engine.delims = render.Delims{Left: left, Right: right} return engine } +// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON. func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { engine.secureJsonPrefix = prefix return engine } +// LoadHTMLGlob loads HTML files identified by glob pattern +// and associates the result with HTML renderer. func (engine *Engine) LoadHTMLGlob(pattern string) { + left := engine.delims.Left + right := engine.delims.Right + 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} return } - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) + + templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) - return } +// LoadHTMLFiles loads a slice of HTML files +// and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} return } + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) - return } +// SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { debugPrintWARNINGSetHTMLTemplate() @@ -181,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} } +// SetFuncMap sets the FuncMap used for template.FuncMap. func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { engine.FuncMap = funcMap } @@ -191,7 +214,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.rebuild404Handlers() } -// NoMethod sets the handlers called when... TODO +// NoMethod sets the handlers called when... TODO. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.noMethod = handlers engine.rebuild405Handlers() @@ -217,7 +240,7 @@ func (engine *Engine) rebuild405Handlers() { func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { 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") debugPrintRoute(method, path, handlers) @@ -268,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. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. -func (engine *Engine) RunTLS(addr 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) defer func() { debugPrintError(err) }() @@ -306,7 +329,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // HandleContext re-enter a context that has been rewritten. -// This can be done by setting c.Request.Path to your new target. +// This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { c.reset() @@ -314,16 +337,13 @@ func (engine *Engine) HandleContext(c *Context) { engine.pool.Put(c) } -func (engine *Engine) handleHTTPRequest(context *Context) { - httpMethod := context.Request.Method - var path string - var unescape bool - if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { - path = context.Request.URL.RawPath +func (engine *Engine) handleHTTPRequest(c *Context) { + httpMethod := c.Request.Method + path := c.Request.URL.Path + unescape := false + if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { + path = c.Request.URL.RawPath unescape = engine.UnescapePathValues - } else { - path = context.Request.URL.Path - unescape = false } // Find root of the tree for the given HTTP method @@ -332,20 +352,20 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if t[i].method == httpMethod { root := t[i].root // 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 { - context.handlers = handlers - context.Params = params - context.Next() - context.writermem.WriteHeaderNow() + c.handlers = handlers + c.Params = params + c.Next() + c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectTrailingSlash { - redirectTrailingSlash(context) + redirectTrailingSlash(c) return } - if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) { + if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } @@ -357,15 +377,15 @@ func (engine *Engine) handleHTTPRequest(context *Context) { for _, tree := range engine.trees { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { - context.handlers = engine.allNoMethod - serveError(context, 405, default405Body) + c.handlers = engine.allNoMethod + serveError(c, 405, default405Body) return } } } } - context.handlers = engine.allNoRoute - serveError(context, 404, default404Body) + c.handlers = engine.allNoRoute + serveError(c, 404, default404Body) } var mimePlain = []string{MIMEPlain} @@ -391,8 +411,8 @@ func redirectTrailingSlash(c *Context) { code = 307 } - if len(path) > 1 && path[len(path)-1] == '/' { - req.URL.Path = path[:len(path)-1] + if length := len(path); length > 1 && path[length-1] == '/' { + req.URL.Path = path[:length-1] } else { req.URL.Path = path + "/" } diff --git a/ginS/gins.go b/ginS/gins.go index d40d1c3a..ee00b381 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -9,15 +9,15 @@ import ( "net/http" "sync" - . "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) var once sync.Once -var internalEngine *Engine +var internalEngine *gin.Engine -func engine() *Engine { +func engine() *gin.Engine { once.Do(func() { - internalEngine = Default() + internalEngine = gin.Default() }) return internalEngine } @@ -35,65 +35,65 @@ func SetHTMLTemplate(templ *template.Template) { } // NoRoute adds handlers for NoRoute. It return a 404 code by default. -func NoRoute(handlers ...HandlerFunc) { +func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } // NoMethod sets the handlers called when... TODO -func NoMethod(handlers ...HandlerFunc) { +func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } // Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // For example, all the routes that use a common middlware for authorization could be grouped. -func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { +func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { 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...) } // 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...) } // 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...) } // 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...) } // 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...) } // 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...) } // 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...) } // 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...) } -func Any(relativePath string, handlers ...HandlerFunc) IRoutes { +func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } -func StaticFile(relativePath, filepath string) IRoutes { +func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } @@ -103,18 +103,18 @@ func StaticFile(relativePath, filepath string) IRoutes { // To use the operating system's file system implementation, // use : // router.Static("/static", "/var/www") -func Static(relativePath, root string) IRoutes { +func Static(relativePath, root string) gin.IRoutes { 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) } // Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. -func Use(middlewares ...HandlerFunc) IRoutes { +func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } diff --git a/gin_integration_test.go b/gin_integration_test.go index f45dd6c1..52f78842 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) { c, err := net.Dial("unix", "/tmp/unix_unit_test") 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) var response string for scanner.Scan() { diff --git a/gin_test.go b/gin_test.go index bdf5a9a9..3ac60577 100644 --- a/gin_test.go +++ b/gin_test.go @@ -5,6 +5,7 @@ package gin import ( + "crypto/tls" "fmt" "html/template" "io/ioutil" @@ -21,9 +22,9 @@ func formatAsDate(t time.Time) string { 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() { - SetMode(TestMode) + SetMode(mode) router := New() router.Delims("{[{", "}]}") 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), }) }) - router.Run(":8888") + 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") + } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } -func setupHTMLGlob(t *testing.T) func() { +func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { go func() { - SetMode(DebugMode) + SetMode(mode) router := New() router.Delims("{[{", "}]}") 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), }) }) - router.Run(":8888") + 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") + } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } -//TODO func TestLoadHTMLGlob(t *testing.T) { - td := setupHTMLGlob(t) + td := setupHTMLGlob(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) @@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) { 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, "